Emit SourcDebugExtension if kept when writing CF

Bug: 152630052
Change-Id: I22372260db71a3f336366f04f9be3d379fa12527
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 688d59b..e04f07c 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -128,7 +128,8 @@
     ClassWriter writer = new ClassWriter(0);
     int markerStringPoolIndex = writer.newConst(markerString);
     assert markerStringPoolIndex == MARKER_STRING_CONSTANT_POOL_INDEX;
-    writer.visitSource(clazz.sourceFile != null ? clazz.sourceFile.toString() : null, null);
+    String sourceDebug = getSourceDebugExtension(clazz.annotations());
+    writer.visitSource(clazz.sourceFile != null ? clazz.sourceFile.toString() : null, sourceDebug);
     int version = getClassFileVersion(clazz);
     int access = clazz.accessFlags.getAsCfAccessFlags();
     String desc = namingLens.lookupDescriptor(clazz.type).toString();
@@ -242,6 +243,16 @@
     return res.toString();
   }
 
+  private String getSourceDebugExtension(DexAnnotationSet annotations) {
+    DexValue debugExtensions =
+        getSystemAnnotationValue(
+            annotations, application.dexItemFactory.annotationSourceDebugExtension);
+    if (debugExtensions == null) {
+      return null;
+    }
+    return debugExtensions.asDexValueString().getValue().toString();
+  }
+
   private ImmutableMap<DexString, DexValue> getAnnotationDefaults(DexAnnotationSet annotations) {
     DexValue value =
         getSystemAnnotationValue(annotations, application.dexItemFactory.annotationDefault);
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index 04a1924..4bb1b2b 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -378,8 +378,7 @@
         }
         for (DexProgramClass clazz : appView.appInfo().classes()) {
           // If [clazz] is mentioned by a keep rule, it could be used for reflection, and we
-          // therefore
-          // need to keep the enclosing method and inner classes attributes, if requested.
+          // therefore need to keep the enclosing method and inner classes attributes, if requested.
           if (appView.appInfo().isPinned(clazz.type)) {
             for (InnerClassAttribute innerClassAttribute : clazz.getInnerClasses()) {
               DexType inner = innerClassAttribute.getInner();
diff --git a/src/test/java/com/android/tools/r8/annotations/SourceDebugExtensionTest.java b/src/test/java/com/android/tools/r8/annotations/SourceDebugExtensionTest.java
new file mode 100644
index 0000000..4a3180b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/annotations/SourceDebugExtensionTest.java
@@ -0,0 +1,75 @@
+// 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.annotations;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.ToolHelper.getFilesInTestFolderRelativeToClass;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.retrace.KotlinInlineFunctionRetraceTest;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.concurrent.ExecutionException;
+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 SourceDebugExtensionTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public SourceDebugExtensionTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+    CfRuntime cfRuntime =
+        parameters.isCfRuntime() ? parameters.getRuntime().asCf() : TestRuntime.getCheckedInJdk9();
+    Path kotlinSources =
+        kotlinc(cfRuntime, getStaticTemp(), KOTLINC, KotlinTargetVersion.JAVA_8)
+            .addSourceFiles(
+                getFilesInTestFolderRelativeToClass(
+                    KotlinInlineFunctionRetraceTest.class, "kt", ".kt"))
+            .compile();
+    CodeInspector kotlinInspector = new CodeInspector(kotlinSources);
+    inspectSourceDebugExtension(kotlinInspector);
+    testForR8(parameters.getBackend())
+        .addProgramFiles(kotlinSources)
+        .addKeepAttributes(ProguardKeepAttributes.SOURCE_DEBUG_EXTENSION)
+        .addKeepAllClassesRule()
+        .setMode(CompilationMode.RELEASE)
+        .compile()
+        .inspect(this::inspectSourceDebugExtension);
+  }
+
+  private void inspectSourceDebugExtension(CodeInspector inspector) {
+    ClassSubject clazz = inspector.clazz("retrace.InlineFunctionKt");
+    assertThat(clazz, isPresent());
+    AnnotationSubject sourceDebugExtensions =
+        clazz.annotation("dalvik.annotation.SourceDebugExtension");
+    assertThat(sourceDebugExtensions, isPresent());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index 98f6b1e..3bbea86 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -102,8 +102,14 @@
 
       @Override
       public void describeMismatchSafely(final Subject subject, Description description) {
-        description
-            .appendText(type(subject) + " ").appendValue(name(subject)).appendText(" was not");
+        if (subject instanceof ClassSubject || subject instanceof MemberSubject) {
+          description
+              .appendText(type(subject) + " ")
+              .appendValue(name(subject))
+              .appendText(" was not");
+        } else {
+          description.appendText(type(subject) + " ").appendText(" was not found");
+        }
       }
     };
   }