Preserve the "Deprecated" attribute for D8 cf-to-cf desugar

Bug: 130421335
Bug: 147485959
Change-Id: Ibf21f54bf829d3bd8aff76efa0be28c612cf67d9
diff --git a/src/main/java/com/android/tools/r8/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
index 74da57f..17a97a3 100644
--- a/src/main/java/com/android/tools/r8/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
@@ -144,7 +144,9 @@
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
               code,
-              50);
+              50,
+              false,
+              false);
       if (method.isStatic() || method.isDirectMethod()) {
         directMethods.add(throwingMethod);
       } else {
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 7683f12..8068417 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -27,6 +27,7 @@
   public final DexField field;
   public final FieldAccessFlags accessFlags;
   private DexValue staticValue;
+  private final boolean deprecated;
 
   private FieldOptimizationInfo optimizationInfo = DefaultFieldOptimizationInfo.getInstance();
   private KotlinFieldLevelInfo kotlinMemberInfo = NO_KOTLIN_INFO;
@@ -35,17 +36,31 @@
       DexField field,
       FieldAccessFlags accessFlags,
       DexAnnotationSet annotations,
-      DexValue staticValue) {
+      DexValue staticValue,
+      boolean deprecated) {
     super(annotations);
     this.field = field;
     this.accessFlags = accessFlags;
     this.staticValue = staticValue;
+    this.deprecated = deprecated;
+  }
+
+  public DexEncodedField(
+      DexField field,
+      FieldAccessFlags accessFlags,
+      DexAnnotationSet annotations,
+      DexValue staticValue) {
+    this(field, accessFlags, annotations, staticValue, false);
   }
 
   public DexType type() {
     return field.type;
   }
 
+  public boolean isDeprecated() {
+    return deprecated;
+  }
+
   public boolean isProgramField(DexDefinitionSupplier definitions) {
     if (field.holder.isClassType()) {
       DexClass clazz = definitions.definitionFor(field.holder);
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index f049ec5..4bc477b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -139,6 +139,7 @@
 
   public final DexMethod method;
   public final MethodAccessFlags accessFlags;
+  public final boolean deprecated;
   public ParameterAnnotationsList parameterAnnotationsList;
   private Code code;
   // TODO(b/128967328): towards finer-grained inlining constraints,
@@ -227,17 +228,7 @@
       DexAnnotationSet annotations,
       ParameterAnnotationsList parameterAnnotationsList,
       Code code) {
-    this(method, accessFlags, annotations, parameterAnnotationsList, code, -1);
-  }
-
-  public DexEncodedMethod(
-      DexMethod method,
-      MethodAccessFlags accessFlags,
-      DexAnnotationSet annotations,
-      ParameterAnnotationsList parameterAnnotationsList,
-      Code code,
-      int classFileVersion) {
-    this(method, accessFlags, annotations, parameterAnnotationsList, code, classFileVersion, false);
+    this(method, accessFlags, annotations, parameterAnnotationsList, code, -1, false);
   }
 
   public DexEncodedMethod(
@@ -258,9 +249,30 @@
       Code code,
       int classFileVersion,
       boolean d8R8Synthesized) {
+    this(
+        method,
+        accessFlags,
+        annotations,
+        parameterAnnotationsList,
+        code,
+        classFileVersion,
+        d8R8Synthesized,
+        false);
+  }
+
+  public DexEncodedMethod(
+      DexMethod method,
+      MethodAccessFlags accessFlags,
+      DexAnnotationSet annotations,
+      ParameterAnnotationsList parameterAnnotationsList,
+      Code code,
+      int classFileVersion,
+      boolean d8R8Synthesized,
+      boolean deprecated) {
     super(annotations);
     this.method = method;
     this.accessFlags = accessFlags;
+    this.deprecated = deprecated;
     this.parameterAnnotationsList = parameterAnnotationsList;
     this.code = code;
     this.classFileVersion = classFileVersion;
@@ -270,6 +282,10 @@
     assert parameterAnnotationsList != null;
   }
 
+  public boolean isDeprecated() {
+    return deprecated;
+  }
+
   public void hashSyntheticContent(Hasher hasher) {
     // Method holder does not contribute to the synthetic hash (it is freely chosen).
     // Method name does not contribute to the synthetic hash (it is freely chosen).
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index ca7a264..68da447 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -45,6 +45,7 @@
   private final ProgramResource.Kind originKind;
   private final Collection<DexProgramClass> synthesizedFrom;
   private int initialClassFileVersion = -1;
+  private boolean deprecated = false;
   private KotlinClassLevelInfo kotlinInfo = NO_KOTLIN_INFO;
 
   private final ChecksumSupplier checksumSupplier;
@@ -516,6 +517,14 @@
     return initialClassFileVersion;
   }
 
+  public void setDeprecated() {
+    deprecated = true;
+  }
+
+  public boolean isDeprecated() {
+    return deprecated;
+  }
+
   /**
    * Is the class reachability sensitive.
    *
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 2b38f4f..a29fb97 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -35,6 +35,7 @@
 import com.android.tools.r8.jar.CfApplicationWriter;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.AsmUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.FieldSignatureEquivalence;
@@ -195,6 +196,7 @@
 
     // DexClass data.
     private int version;
+    private boolean deprecated;
     private DexType type;
     private ClassAccessFlags accessFlags;
     private DexType superType;
@@ -301,6 +303,7 @@
       if (InternalOptions.SUPPORTED_CF_MAJOR_VERSION < getMajorVersion()) {
         throw new CompilationError("Unsupported class file version: " + getMajorVersion(), origin);
       }
+      this.deprecated = AsmUtils.isDeprecated(access);
       accessFlags = ClassAccessFlags.fromCfAccessFlags(cleanAccessFlags(access));
       type = application.getTypeFromName(name);
       // Check if constraints from
@@ -449,6 +452,9 @@
       if (clazz.isProgramClass()) {
         DexProgramClass programClass = clazz.asProgramClass();
         programClass.setInitialClassFileVersion(version);
+        if (deprecated) {
+          programClass.setDeprecated();
+        }
       }
       classConsumer.accept(clazz);
     }
@@ -587,7 +593,9 @@
         DexAnnotationSet annotationSet =
             createAnnotationSet(annotations, parent.application.options);
         DexValue staticValue = flags.isStatic() ? getStaticValue(value, dexField.type) : null;
-        DexEncodedField field = new DexEncodedField(dexField, flags, annotationSet, staticValue);
+        DexEncodedField field =
+            new DexEncodedField(
+                dexField, flags, annotationSet, staticValue, AsmUtils.isDeprecated(access));
         if (flags.isStatic()) {
           parent.staticFields.add(field);
         } else {
@@ -662,6 +670,7 @@
     private List<DexValue> parameterFlags = null;
     final DexMethod method;
     final MethodAccessFlags flags;
+    final boolean deprecated;
     Code code = null;
 
     public CreateMethodVisitor(int access, String name, String desc, String signature,
@@ -671,6 +680,7 @@
       this.parent = parent;
       this.method = parent.application.getMethod(parent.type, name, desc);
       this.flags = createMethodAccessFlags(name, access);
+      this.deprecated = AsmUtils.isDeprecated(access);
       parameterCount = DescriptorUtils.getArgumentCount(desc);
       if (exceptions != null && exceptions.length > 0) {
         DexValue[] values = new DexValue[exceptions.length];
@@ -813,7 +823,9 @@
               createAnnotationSet(annotations, options),
               parameterAnnotationsList,
               code,
-              parent.version);
+              parent.version,
+              false,
+              deprecated);
       Wrapper<DexMethod> signature = MethodSignatureEquivalence.get().wrap(method);
       if (parent.methodSignatures.add(signature)) {
         parent.hasReachabilitySensitiveMethod |= isReachabilitySensitive();
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 9ccc19c..e28734b 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -43,6 +43,7 @@
 import com.android.tools.r8.naming.ProguardMapSupplier;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.utils.AsmUtils;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableMap;
@@ -173,6 +174,9 @@
       }
     }
     int access = clazz.accessFlags.getAsCfAccessFlags();
+    if (clazz.isDeprecated()) {
+      access = AsmUtils.withDeprecated(access);
+    }
     String desc = namingLens.lookupDescriptor(clazz.type).toString();
     String name = namingLens.lookupInternalName(clazz.type);
     String signature = getSignature(clazz.annotations());
@@ -326,6 +330,9 @@
 
   private void writeField(DexEncodedField field, ClassWriter writer) {
     int access = field.accessFlags.getAsCfAccessFlags();
+    if (field.isDeprecated()) {
+      access = AsmUtils.withDeprecated(access);
+    }
     String name = namingLens.lookupName(field.field).toString();
     String desc = namingLens.lookupDescriptor(field.field.type).toString();
     String signature = getSignature(field.annotations());
@@ -343,6 +350,9 @@
       ImmutableMap<DexString, DexValue> defaults) {
     DexEncodedMethod definition = method.getDefinition();
     int access = definition.getAccessFlags().getAsCfAccessFlags();
+    if (definition.isDeprecated()) {
+      access = AsmUtils.withDeprecated(access);
+    }
     String name = namingLens.lookupName(method.getReference()).toString();
     String desc = definition.descriptor(namingLens);
     String signature = getSignature(definition.annotations());
diff --git a/src/main/java/com/android/tools/r8/utils/AsmUtils.java b/src/main/java/com/android/tools/r8/utils/AsmUtils.java
new file mode 100644
index 0000000..44fcc6f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/AsmUtils.java
@@ -0,0 +1,23 @@
+// 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.utils;
+
+import static org.objectweb.asm.Opcodes.ACC_DEPRECATED;
+
+public class AsmUtils {
+  public static boolean isDeprecated(int access) {
+    // ASM stores the Deprecated attribute
+    // (https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.15) in the
+    // access flags.
+    return (access & ACC_DEPRECATED) == ACC_DEPRECATED;
+  }
+
+  public static int withDeprecated(int access) {
+    // ASM stores the Deprecated attribute
+    // (https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.15) in the
+    // access flags.
+    return access | ACC_DEPRECATED;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileDeprecatedAttribute.java b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileDeprecatedAttribute.java
new file mode 100644
index 0000000..0eac295
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileDeprecatedAttribute.java
@@ -0,0 +1,128 @@
+// 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.desugar;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ByteDataView;
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.InternalOptions;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class DesugarToClassFileDeprecatedAttribute extends TestBase {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  private final TestParameters parameters;
+
+  public DesugarToClassFileDeprecatedAttribute(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private boolean isDeprecated(int access) {
+    return (access & Opcodes.ACC_DEPRECATED) == Opcodes.ACC_DEPRECATED;
+  }
+
+  private void checkDeprecatedAttributes(byte[] classBytes) {
+    ClassReader cr = new ClassReader(classBytes);
+    cr.accept(
+        new ClassVisitor(InternalOptions.ASM_VERSION) {
+          @Override
+          public void visit(
+              int version,
+              int access,
+              String name,
+              String signature,
+              String superName,
+              String[] interfaces) {
+            assertTrue(isDeprecated(access));
+          }
+
+          @Override
+          public MethodVisitor visitMethod(
+              int access, String name, String desc, String signature, String[] exceptions) {
+            if (!name.equals("<init>")) {
+              assertTrue(isDeprecated(access));
+            }
+            return super.visitMethod(access, name, desc, signature, exceptions);
+          }
+
+          @Override
+          public FieldVisitor visitField(
+              int access, String name, String descriptor, String signature, Object value) {
+            assertTrue(isDeprecated(access));
+            return super.visitField(access, name, descriptor, signature, value);
+          }
+        },
+        0);
+  }
+
+  @Test
+  public void test() throws Exception {
+    checkDeprecatedAttributes(
+        Files.readAllBytes(ToolHelper.getClassFileForTestClass(TestClass.class)));
+
+    // Use D8 to desugar with Java classfile output.
+    Path jar =
+        testForD8(Backend.CF)
+            .addProgramClasses(TestClass.class)
+            .setMinApi(parameters.getApiLevel())
+            .setProgramConsumer(
+                new ClassFileConsumer.ForwardingConsumer(null) {
+                  @Override
+                  public void accept(
+                      ByteDataView data, String descriptor, DiagnosticsHandler handler) {
+                    checkDeprecatedAttributes(data.getBuffer());
+                  }
+                })
+            .compile()
+            .writeToZip();
+
+    if (parameters.getRuntime().isCf()) {
+      // Run on the JVM.
+      testForJvm()
+          .addProgramFiles(jar)
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutputLines("Hello, world!");
+    } else {
+      assert parameters.getRuntime().isDex();
+      // Convert to DEX without desugaring.
+      testForD8()
+          .addProgramFiles(jar)
+          .setMinApi(parameters.getApiLevel())
+          .disableDesugaring()
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutputLines("Hello, world!");
+    }
+  }
+
+  @Deprecated
+  public static class TestClass {
+    @Deprecated public Object object = new Object();
+
+    @Deprecated
+    public static void main(String[] args) {
+      System.out.println("Hello, world!");
+    }
+  }
+}