| // Copyright (c) 2020, 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.tracereferences; |
| |
| import static com.android.tools.r8.utils.MissingDefinitionsDiagnosticTestUtils.getMissingClassMessage; |
| import static com.android.tools.r8.utils.MissingDefinitionsDiagnosticTestUtils.getMissingFieldMessage; |
| import static com.android.tools.r8.utils.MissingDefinitionsDiagnosticTestUtils.getMissingMethodMessage; |
| import static org.junit.Assert.fail; |
| |
| import com.android.tools.r8.CompilationFailedException; |
| import com.android.tools.r8.DiagnosticsHandler; |
| import com.android.tools.r8.TestBase; |
| import com.android.tools.r8.TestDiagnosticMessagesImpl; |
| import com.android.tools.r8.TestParameters; |
| import com.android.tools.r8.TestParametersCollection; |
| import com.android.tools.r8.ToolHelper; |
| import com.android.tools.r8.diagnostic.DefinitionContext; |
| import com.android.tools.r8.diagnostic.internal.DefinitionMethodContextImpl; |
| import com.android.tools.r8.references.Reference; |
| import com.android.tools.r8.tracereferences.TraceReferencesCommandTest.Source; |
| import com.android.tools.r8.utils.AndroidApiLevel; |
| import com.android.tools.r8.utils.DescriptorUtils; |
| import com.android.tools.r8.utils.StringDiagnostic; |
| import com.android.tools.r8.utils.StringUtils; |
| import com.android.tools.r8.utils.ZipUtils.ZipBuilder; |
| import java.nio.file.Path; |
| 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 TraceReferencesDiagnosticTest extends TestBase { |
| @Parameters(name = "{0}") |
| public static TestParametersCollection data() { |
| return getTestParameters().withNoneRuntime().build(); |
| } |
| |
| public TraceReferencesDiagnosticTest(TestParameters parameters) { |
| parameters.assertNoneRuntime(); |
| } |
| |
| @Test |
| public void traceReferencesDiagnosticClassesFieldsAndMethods() throws Throwable { |
| Path dir = temp.newFolder().toPath(); |
| Path targetJar = |
| ZipBuilder.builder(dir.resolve("target.jar")) |
| .addBytes( |
| DescriptorUtils.getPathFromJavaType(Target.class), |
| transformer(Target.class) |
| .removeFields( |
| (access, name, descriptor, signature, value) -> |
| name.equals("missingField1")) |
| .removeFields( |
| (access, name, descriptor, signature, value) -> |
| name.equals("missingField2")) |
| .removeMethods( |
| (access, name, descriptor, signature, exceptions) -> |
| name.equals("missingMethod")) |
| .transform()) |
| .build(); |
| Path sourceJar = |
| ZipBuilder.builder(dir.resolve("source.jar")) |
| .addFilesRelative( |
| ToolHelper.getClassPathForTests(), |
| ToolHelper.getClassFileForTestClass(Source.class)) |
| .build(); |
| |
| TestDiagnosticMessagesImpl testDiagnosticMessages = new TestDiagnosticMessagesImpl(); |
| try { |
| TraceReferences.run( |
| TraceReferencesCommand.builder(testDiagnosticMessages) |
| .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P)) |
| .addSourceFiles(sourceJar) |
| .addTargetFiles(targetJar) |
| .setConsumer( |
| new TraceReferencesCheckConsumer(TraceReferencesConsumer.emptyConsumer())) |
| .build()); |
| fail("Unexpected success"); |
| } catch (CompilationFailedException e) { |
| // Expected. |
| } |
| |
| DefinitionContext referencedFrom = |
| DefinitionMethodContextImpl.builder() |
| .setMethodContext(Reference.methodFromMethod(Source.class.getDeclaredMethod("source"))) |
| .setOrigin(getOrigin(Source.class)) |
| .build(); |
| testDiagnosticMessages.inspectErrors( |
| diagnostic -> |
| diagnostic |
| .assertIsMissingDefinitionsDiagnostic() |
| .assertHasMessage( |
| StringUtils.joinLines( |
| getMissingClassMessage(Target1.class, referencedFrom), |
| getMissingMethodMessage( |
| Reference.methodFromMethod(Target1.class.getDeclaredConstructor()), |
| referencedFrom), |
| getMissingClassMessage(Target2.class, referencedFrom), |
| getMissingMethodMessage( |
| Reference.methodFromMethod(Target2.class.getDeclaredConstructor()), |
| referencedFrom), |
| getMissingClassMessage(Target3.class, referencedFrom), |
| getMissingMethodMessage( |
| Reference.methodFromMethod(Target3.class.getDeclaredConstructor()), |
| referencedFrom), |
| getMissingFieldMessage( |
| Reference.fieldFromField( |
| Target.class.getDeclaredField("missingField1")), |
| referencedFrom), |
| getMissingFieldMessage( |
| Reference.fieldFromField( |
| Target.class.getDeclaredField("missingField2")), |
| referencedFrom), |
| getMissingMethodMessage( |
| Reference.methodFromMethod( |
| Target.class.getDeclaredMethod("missingMethod")), |
| referencedFrom))) |
| .assertIsAllMissingClasses(Target1.class, Target2.class, Target3.class) |
| .assertIsAllMissingFields( |
| Reference.fieldFromField(Target.class.getField("missingField1")), |
| Reference.fieldFromField(Target.class.getField("missingField2"))) |
| .assertIsAllMissingMethods( |
| Reference.methodFromMethod(Target1.class.getDeclaredConstructor()), |
| Reference.methodFromMethod(Target2.class.getDeclaredConstructor()), |
| Reference.methodFromMethod(Target3.class.getDeclaredConstructor()), |
| Reference.methodFromMethod(Target.class.getMethod("missingMethod")))); |
| } |
| |
| @Test |
| public void traceReferencesDiagnosticFieldsAndMethods() throws Throwable { |
| Path dir = temp.newFolder().toPath(); |
| Path targetJar = |
| ZipBuilder.builder(dir.resolve("target.jar")) |
| .addFilesRelative( |
| ToolHelper.getClassPathForTests(), |
| ToolHelper.getClassFileForTestClass(Target1.class), |
| ToolHelper.getClassFileForTestClass(Target2.class), |
| ToolHelper.getClassFileForTestClass(Target3.class)) |
| .addBytes( |
| DescriptorUtils.getPathFromJavaType(Target.class), |
| transformer(Target.class) |
| .removeFields( |
| (access, name, descriptor, signature, value) -> |
| name.equals("missingField1")) |
| .removeFields( |
| (access, name, descriptor, signature, value) -> |
| name.equals("missingField2")) |
| .removeMethods( |
| (access, name, descriptor, signature, exceptions) -> |
| name.equals("missingMethod")) |
| .transform()) |
| .build(); |
| Path sourceJar = |
| ZipBuilder.builder(dir.resolve("source.jar")) |
| .addFilesRelative( |
| ToolHelper.getClassPathForTests(), |
| ToolHelper.getClassFileForTestClass(Source.class)) |
| .build(); |
| |
| TestDiagnosticMessagesImpl testDiagnosticMessages = new TestDiagnosticMessagesImpl(); |
| try { |
| TraceReferences.run( |
| TraceReferencesCommand.builder(testDiagnosticMessages) |
| .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P)) |
| .addSourceFiles(sourceJar) |
| .addTargetFiles(targetJar) |
| .setConsumer( |
| new TraceReferencesCheckConsumer(TraceReferencesConsumer.emptyConsumer())) |
| .build()); |
| fail("Unexpected success"); |
| } catch (CompilationFailedException e) { |
| // Expected. |
| } |
| |
| DefinitionContext referencedFrom = |
| DefinitionMethodContextImpl.builder() |
| .setMethodContext(Reference.methodFromMethod(Source.class.getDeclaredMethod("source"))) |
| .setOrigin(getOrigin(Source.class)) |
| .build(); |
| testDiagnosticMessages.inspectErrors( |
| diagnostic -> |
| diagnostic |
| .assertIsMissingDefinitionsDiagnostic() |
| .assertHasMessage( |
| StringUtils.joinLines( |
| getMissingFieldMessage( |
| Reference.fieldFromField( |
| Target.class.getDeclaredField("missingField1")), |
| referencedFrom), |
| getMissingFieldMessage( |
| Reference.fieldFromField( |
| Target.class.getDeclaredField("missingField2")), |
| referencedFrom), |
| getMissingMethodMessage( |
| Reference.methodFromMethod( |
| Target.class.getDeclaredMethod("missingMethod")), |
| referencedFrom))) |
| .assertNoMissingClasses() |
| .assertIsAllMissingFields( |
| Reference.fieldFromField(Target.class.getField("missingField1")), |
| Reference.fieldFromField(Target.class.getField("missingField2"))) |
| .assertIsAllMissingMethods( |
| Reference.methodFromMethod(Target.class.getMethod("missingMethod")))); |
| } |
| |
| @Test |
| public void traceReferencesDiagnosticMethods() throws Throwable { |
| Path dir = temp.newFolder().toPath(); |
| Path targetJar = |
| ZipBuilder.builder(dir.resolve("target.jar")) |
| .addFilesRelative( |
| ToolHelper.getClassPathForTests(), |
| ToolHelper.getClassFileForTestClass(Target1.class), |
| ToolHelper.getClassFileForTestClass(Target2.class), |
| ToolHelper.getClassFileForTestClass(Target3.class)) |
| .addBytes( |
| DescriptorUtils.getPathFromJavaType(Target.class), |
| transformer(Target.class) |
| .removeMethods( |
| (access, name, descriptor, signature, exceptions) -> |
| name.equals("missingMethod")) |
| .transform()) |
| .build(); |
| Path sourceJar = |
| ZipBuilder.builder(dir.resolve("source.jar")) |
| .addFilesRelative( |
| ToolHelper.getClassPathForTests(), |
| ToolHelper.getClassFileForTestClass(Source.class)) |
| .build(); |
| |
| TestDiagnosticMessagesImpl testDiagnosticMessages = new TestDiagnosticMessagesImpl(); |
| try { |
| TraceReferences.run( |
| TraceReferencesCommand.builder(testDiagnosticMessages) |
| .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P)) |
| .addSourceFiles(sourceJar) |
| .addTargetFiles(targetJar) |
| .setConsumer( |
| new TraceReferencesCheckConsumer(TraceReferencesConsumer.emptyConsumer())) |
| .build()); |
| fail("Unexpected success"); |
| } catch (CompilationFailedException e) { |
| // Expected. |
| } |
| |
| DefinitionContext referencedFrom = |
| DefinitionMethodContextImpl.builder() |
| .setMethodContext(Reference.methodFromMethod(Source.class.getDeclaredMethod("source"))) |
| .setOrigin(getOrigin(Source.class)) |
| .build(); |
| testDiagnosticMessages.inspectErrors( |
| diagnostic -> |
| diagnostic |
| .assertIsMissingDefinitionsDiagnostic() |
| .assertHasMessage( |
| getMissingMethodMessage( |
| Reference.methodFromMethod(Target.class.getDeclaredMethod("missingMethod")), |
| referencedFrom)) |
| .assertNoMissingClasses() |
| .assertNoMissingFields() |
| .assertIsAllMissingMethods( |
| Reference.methodFromMethod(Target.class.getMethod("missingMethod")))); |
| } |
| |
| static class FailingConsumer implements TraceReferencesConsumer { |
| private final String where; |
| private boolean reported; |
| |
| FailingConsumer(String where) { |
| this.where = where; |
| } |
| |
| @Override |
| public void acceptType(TracedClass tracedClass, DiagnosticsHandler handler) { |
| if (!reported && where.equals("acceptType")) { |
| handler.error(new StringDiagnostic("Error in " + where)); |
| reported = true; |
| } |
| } |
| |
| @Override |
| public void acceptField(TracedField tracedField, DiagnosticsHandler handler) { |
| if (!reported && where.equals("acceptField")) { |
| handler.error(new StringDiagnostic("Error in " + where)); |
| reported = true; |
| } |
| } |
| |
| @Override |
| public void acceptMethod(TracedMethod tracedMethod, DiagnosticsHandler handler) { |
| if (!reported && where.equals("acceptMethod")) { |
| handler.error(new StringDiagnostic("Error in " + where)); |
| reported = true; |
| } |
| } |
| |
| @Override |
| public void finished(DiagnosticsHandler handler) { |
| if (!reported && where.equals("finished")) { |
| handler.error(new StringDiagnostic("Error in " + where)); |
| reported = true; |
| } |
| } |
| } |
| |
| @Test |
| public void traceReferencesConsumerError() throws Throwable { |
| Path dir = temp.newFolder().toPath(); |
| Path targetJar = |
| ZipBuilder.builder(dir.resolve("target.jar")) |
| .addFilesRelative( |
| ToolHelper.getClassPathForTests(), |
| ToolHelper.getClassFileForTestClass(Target.class), |
| ToolHelper.getClassFileForTestClass(Target1.class), |
| ToolHelper.getClassFileForTestClass(Target2.class), |
| ToolHelper.getClassFileForTestClass(Target3.class)) |
| .build(); |
| Path sourceJar = |
| ZipBuilder.builder(dir.resolve("source.jar")) |
| .addFilesRelative( |
| ToolHelper.getClassPathForTests(), |
| ToolHelper.getClassFileForTestClass(Source.class)) |
| .build(); |
| |
| for (String where : new String[] {"acceptType", "acceptField", "acceptMethod", "finished"}) { |
| TestDiagnosticMessagesImpl testDiagnosticMessages = new TestDiagnosticMessagesImpl(); |
| try { |
| TraceReferences.run( |
| TraceReferencesCommand.builder(testDiagnosticMessages) |
| .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P)) |
| .addSourceFiles(sourceJar) |
| .addTargetFiles(targetJar) |
| .setConsumer(new FailingConsumer(where)) |
| .build()); |
| fail("Unexpected success"); |
| } catch (CompilationFailedException e) { |
| // Expected. |
| } |
| |
| testDiagnosticMessages.inspectErrors( |
| diagnostic -> |
| diagnostic.assertIsStringDiagnostic().assertHasMessage("Error in " + where)); |
| } |
| } |
| |
| static class Target1 {} |
| |
| static class Target2 {} |
| |
| static class Target3 {} |
| |
| static class Target { |
| public static int missingField1; |
| public static int missingField2; |
| |
| public static void missingMethod() {} |
| } |
| |
| static class Source { |
| public static void source() { |
| new Target1(); |
| new Target2(); |
| new Target3(); |
| |
| Target.missingField1 = 1; |
| Target.missingField2 = 2; |
| Target.missingMethod(); |
| } |
| } |
| } |