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!");
+ }
+ }
+}