| // Copyright (c) 2018, 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.naming.retraceproguard; |
| |
| import static com.android.tools.r8.naming.retraceproguard.StackTrace.isSameExceptForFileName; |
| import static com.android.tools.r8.naming.retraceproguard.StackTrace.isSameExceptForFileNameAndLineNumber; |
| import static org.hamcrest.MatcherAssert.assertThat; |
| import static org.junit.Assert.assertEquals; |
| |
| import com.android.tools.r8.CompilationMode; |
| import com.android.tools.r8.ToolHelper; |
| import com.android.tools.r8.naming.retraceproguard.StackTrace.StackTraceLine; |
| import com.android.tools.r8.utils.BooleanUtils; |
| import com.google.common.collect.ImmutableList; |
| import java.util.Collection; |
| import org.junit.Ignore; |
| 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 DesugarLambdaRetraceTest extends RetraceTestBase { |
| |
| @Parameters(name = "Backend: {0}, mode: {1}, compat: {2}") |
| public static Collection<Object[]> data() { |
| return buildParameters( |
| ToolHelper.getBackends(), CompilationMode.values(), BooleanUtils.values()); |
| } |
| |
| public DesugarLambdaRetraceTest(Backend backend, CompilationMode mode, boolean compat) { |
| super(backend, mode, compat); |
| } |
| |
| @Override |
| public Collection<Class<?>> getClasses() { |
| return ImmutableList.of(getMainClass(), ConsumerDesugarLambdaRetraceTest.class); |
| } |
| |
| @Override |
| public Class<?> getMainClass() { |
| return MainDesugarLambdaRetraceTest.class; |
| } |
| |
| private int expectedActualStackTraceHeight() { |
| // In debug mode the expected stack trace height differs since there is no lambda desugaring |
| // for CF. |
| return mode == CompilationMode.RELEASE ? 2 : (backend == Backend.CF ? 4 : 5); |
| } |
| |
| private boolean isSynthesizedLambdaFrame(StackTraceLine line) { |
| return line.className.contains("-$$Lambda$"); |
| } |
| |
| private void checkLambdaFrame(StackTrace retracedStackTrace) { |
| StackTrace lambdaFrames = retracedStackTrace.filter(this::isSynthesizedLambdaFrame); |
| assertEquals(1, lambdaFrames.size()); |
| if (lambdaFrames.get(0).hasLineNumber()) { |
| assertEquals(mode == CompilationMode.RELEASE ? 0 : 2, lambdaFrames.get(0).lineNumber); |
| } |
| // Proguard retrace will take the class name until the first $ to construct the file |
| // name, so for "-$$Lambda$...", the file name becomes "-.java". |
| assertEquals("-.java", lambdaFrames.get(0).fileName); |
| } |
| |
| private void checkIsSameExceptForFileName( |
| StackTrace actualStackTrace, StackTrace retracedStackTrace) { |
| // Even when SourceFile is present retrace replaces the file name in the stack trace. |
| if (backend == Backend.CF) { |
| // TODO(122440196): Additional code to locate issue. |
| if (!isSameExceptForFileName(expectedStackTrace).matches(retracedStackTrace)) { |
| System.out.println("Expected original:"); |
| System.out.println(expectedStackTrace.getOriginalStderr()); |
| System.out.println("Actual original:"); |
| System.out.println(retracedStackTrace.getOriginalStderr()); |
| System.out.println("Parsed original:"); |
| System.out.println(expectedStackTrace); |
| System.out.println("Parsed retraced:"); |
| System.out.println(retracedStackTrace); |
| } |
| assertThat(retracedStackTrace, isSameExceptForFileName(expectedStackTrace)); |
| } else { |
| // TODO(122440196): Additional code to locate issue. |
| if (!isSameExceptForFileName(expectedStackTrace) |
| .matches(retracedStackTrace.filter(line -> !isSynthesizedLambdaFrame(line)))) { |
| System.out.println("Expected original:"); |
| System.out.println(expectedStackTrace.getOriginalStderr()); |
| System.out.println("Actual original:"); |
| System.out.println(retracedStackTrace.getOriginalStderr()); |
| System.out.println("Parsed original:"); |
| System.out.println(expectedStackTrace); |
| System.out.println("Parsed retraced:"); |
| System.out.println(retracedStackTrace); |
| } |
| // With the frame from the lambda class filtered out the stack trace is the same. |
| assertThat( |
| retracedStackTrace.filter(line -> !isSynthesizedLambdaFrame(line)), |
| isSameExceptForFileName(expectedStackTrace)); |
| // Check the frame from the lambda class. |
| checkLambdaFrame(retracedStackTrace); |
| } |
| assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size()); |
| } |
| |
| private void checkIsSameExceptForFileNameAndLineNumber( |
| StackTrace actualStackTrace, StackTrace retracedStackTrace) { |
| // Even when SourceFile is present retrace replaces the file name in the stack trace. |
| if (backend == Backend.CF) { |
| // TODO(122440196): Additional code to locate issue. |
| if (!isSameExceptForFileNameAndLineNumber(expectedStackTrace).matches(retracedStackTrace)) { |
| System.out.println("Expected original:"); |
| System.out.println(expectedStackTrace.getOriginalStderr()); |
| System.out.println("Actual original:"); |
| System.out.println(retracedStackTrace.getOriginalStderr()); |
| System.out.println("Parsed original:"); |
| System.out.println(expectedStackTrace); |
| System.out.println("Parsed retraced:"); |
| System.out.println(retracedStackTrace); |
| } |
| assertThat(retracedStackTrace, isSameExceptForFileNameAndLineNumber(expectedStackTrace)); |
| } else { |
| // TODO(122440196): Additional code to locate issue. |
| if (!isSameExceptForFileNameAndLineNumber(expectedStackTrace) |
| .matches(retracedStackTrace.filter(line -> !isSynthesizedLambdaFrame(line)))) { |
| System.out.println("Expected original:"); |
| System.out.println(expectedStackTrace.getOriginalStderr()); |
| System.out.println("Actual original:"); |
| System.out.println(retracedStackTrace.getOriginalStderr()); |
| System.out.println("Parsed original:"); |
| System.out.println(expectedStackTrace); |
| System.out.println("Parsed retraced:"); |
| System.out.println(retracedStackTrace); |
| } |
| // With the frame from the lambda class filtered out the stack trace is the same. |
| assertThat( |
| retracedStackTrace.filter(line -> !isSynthesizedLambdaFrame(line)), |
| isSameExceptForFileNameAndLineNumber(expectedStackTrace)); |
| // Check the frame from the lambda class. |
| checkLambdaFrame(retracedStackTrace); |
| } |
| assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size()); |
| } |
| |
| @Test |
| @Ignore("b/122440196") |
| public void testSourceFileAndLineNumberTable() throws Exception { |
| runTest( |
| ImmutableList.of("-keepattributes SourceFile,LineNumberTable"), |
| this::checkIsSameExceptForFileName); |
| } |
| |
| @Test |
| @Ignore("b/122440196") |
| public void testLineNumberTableOnly() throws Exception { |
| runTest( |
| ImmutableList.of("-keepattributes LineNumberTable"), this::checkIsSameExceptForFileName); |
| } |
| |
| @Test |
| @Ignore("b/122440196") |
| public void testNoLineNumberTable() throws Exception { |
| runTest(ImmutableList.of(), this::checkIsSameExceptForFileNameAndLineNumber); |
| } |
| } |
| |
| // Custom consumer functional interface, as java.util.function.Consumer is not present on Android. |
| @FunctionalInterface |
| interface ConsumerDesugarLambdaRetraceTest<T> { |
| void accept(T value); |
| } |
| |
| class MainDesugarLambdaRetraceTest { |
| |
| public static void method2(long j) { |
| System.out.println("In method2"); |
| if (j == 1) throw null; |
| } |
| |
| public static void method1(String s, ConsumerDesugarLambdaRetraceTest<String> consumer) { |
| System.out.println("In method1"); |
| for (int i = 0; i < 10; i++) { |
| consumer.accept(s); |
| } |
| } |
| |
| public static void main(String[] args) { |
| System.out.println("In main"); |
| method1("1", s -> method2(Long.parseLong(s))); |
| } |
| } |