CfApplicationWriter: Handle annotations, generic signatures, throws
Fixes Art test 005-annotations.
Change-Id: I0a3eabbf3abf0df3f78e9846aa66b12a2bc806df
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index b75cf1f..dd08190 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -91,6 +91,8 @@
throw new Unreachable("No default value for unexpected type " + type);
}
+ public abstract Object getBoxedValue();
+
// Returns a const instruction for the non default value.
public Instruction asConstInstruction(boolean hasClassInitializer, Value dest) {
return null;
@@ -141,6 +143,11 @@
}
@Override
+ public Object getBoxedValue() {
+ throw new Unreachable();
+ }
+
+ @Override
public Object asAsmEncodedObject() {
throw new Unreachable();
}
@@ -213,6 +220,11 @@
}
@Override
+ public Object getBoxedValue() {
+ return getValue();
+ }
+
+ @Override
public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
writeHeader(VALUE_BYTE, 0, dest);
dest.putSignedEncodedValue(value, 1);
@@ -265,6 +277,11 @@
}
@Override
+ public Object getBoxedValue() {
+ return getValue();
+ }
+
+ @Override
public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
writeIntegerTo(VALUE_SHORT, value, Short.BYTES, dest);
}
@@ -316,6 +333,11 @@
}
@Override
+ public Object getBoxedValue() {
+ return getValue();
+ }
+
+ @Override
public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
dest.forward(1);
int length = dest.putUnsignedEncodedValue(value, 2);
@@ -371,6 +393,11 @@
}
@Override
+ public Object getBoxedValue() {
+ return getValue();
+ }
+
+ @Override
public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
writeIntegerTo(VALUE_INT, value, Integer.BYTES, dest);
}
@@ -422,6 +449,11 @@
}
@Override
+ public Object getBoxedValue() {
+ return getValue();
+ }
+
+ @Override
public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
writeIntegerTo(VALUE_LONG, value, Long.BYTES, dest);
}
@@ -473,6 +505,11 @@
}
@Override
+ public Object getBoxedValue() {
+ return getValue();
+ }
+
+ @Override
public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
dest.forward(1);
int length = EncodedValueUtils.putFloat(dest, value);
@@ -526,6 +563,11 @@
}
@Override
+ public Object getBoxedValue() {
+ return getValue();
+ }
+
+ @Override
public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
dest.forward(1);
int length = EncodedValueUtils.putDouble(dest, value);
@@ -580,6 +622,11 @@
}
@Override
+ public Object getBoxedValue() {
+ throw new Unreachable("No boxed value for DexValue " + this.getClass().getSimpleName());
+ }
+
+ @Override
public Object asAsmEncodedObject() {
throw new Unreachable("No ASM conversion for DexValue " + this.getClass().getSimpleName());
}
@@ -750,6 +797,11 @@
}
@Override
+ public Object getBoxedValue() {
+ throw new Unreachable("No boxed value for DexValueArray");
+ }
+
+ @Override
public Object asAsmEncodedObject() {
throw new Unreachable("No ASM conversion for DexValueArray");
}
@@ -804,6 +856,11 @@
}
@Override
+ public Object getBoxedValue() {
+ throw new Unreachable("No boxed value for DexValueAnnotation");
+ }
+
+ @Override
public Object asAsmEncodedObject() {
throw new Unreachable("No ASM conversion for DexValueAnnotation");
}
@@ -854,6 +911,11 @@
}
@Override
+ public Object getBoxedValue() {
+ return null;
+ }
+
+ @Override
public Object asAsmEncodedObject() {
return null;
}
@@ -899,6 +961,11 @@
}
@Override
+ public Object getBoxedValue() {
+ return getValue();
+ }
+
+ @Override
public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
writeHeader(VALUE_BOOLEAN, value ? 1 : 0, dest);
}
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 3814e28..4eea8a8 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -8,23 +8,55 @@
import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.dex.ApplicationWriter;
import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationElement;
+import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexEncodedAnnotation;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueAnnotation;
+import com.android.tools.r8.graph.DexValue.DexValueArray;
+import com.android.tools.r8.graph.DexValue.DexValueBoolean;
+import com.android.tools.r8.graph.DexValue.DexValueByte;
+import com.android.tools.r8.graph.DexValue.DexValueChar;
+import com.android.tools.r8.graph.DexValue.DexValueDouble;
+import com.android.tools.r8.graph.DexValue.DexValueEnum;
+import com.android.tools.r8.graph.DexValue.DexValueField;
+import com.android.tools.r8.graph.DexValue.DexValueFloat;
+import com.android.tools.r8.graph.DexValue.DexValueInt;
+import com.android.tools.r8.graph.DexValue.DexValueLong;
+import com.android.tools.r8.graph.DexValue.DexValueMethod;
+import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
+import com.android.tools.r8.graph.DexValue.DexValueMethodType;
+import com.android.tools.r8.graph.DexValue.DexValueNull;
+import com.android.tools.r8.graph.DexValue.DexValueShort;
+import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.graph.DexValue.UnknownDexValue;
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.naming.ProguardMapSupplier;
import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMap.Builder;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.concurrent.ExecutorService;
+import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.util.CheckClassAdapter;
@@ -87,7 +119,7 @@
int access = clazz.accessFlags.getAsCfAccessFlags();
String desc = clazz.type.toDescriptorString();
String name = clazz.type.getInternalName();
- String signature = null; // TODO(zerny): Support generic signatures.
+ String signature = getSignature(clazz.annotations);
String superName =
clazz.type == options.itemFactory.objectType ? null : clazz.superType.getInternalName();
String[] interfaces = new String[clazz.interfaces.values.length];
@@ -95,6 +127,8 @@
interfaces[i] = clazz.interfaces.values[i].getInternalName();
}
writer.visit(version, access, name, signature, superName, interfaces);
+ writeAnnotations(writer::visitAnnotation, clazz.annotations.annotations);
+ ImmutableMap<DexString, DexValue> defaults = getAnnotationDefaults(clazz.annotations);
if (clazz.getEnclosingMethod() != null) {
clazz.getEnclosingMethod().write(writer);
@@ -111,10 +145,10 @@
writeField(field, writer);
}
for (DexEncodedMethod method : clazz.directMethods()) {
- writeMethod(method, writer);
+ writeMethod(method, writer, defaults);
}
for (DexEncodedMethod method : clazz.virtualMethods()) {
- writeMethod(method, writer);
+ writeMethod(method, writer, defaults);
}
writer.visitEnd();
@@ -132,6 +166,62 @@
options.reporter, handler -> consumer.accept(result, desc, handler));
}
+ private DexValue getSystemAnnotationValue(DexAnnotationSet annotations, DexType type) {
+ DexAnnotation annotation = annotations.getFirstMatching(type);
+ if (annotation == null) {
+ return null;
+ }
+ assert annotation.visibility == DexAnnotation.VISIBILITY_SYSTEM;
+ DexEncodedAnnotation encodedAnnotation = annotation.annotation;
+ assert encodedAnnotation.elements.length == 1;
+ return encodedAnnotation.elements[0].value;
+ }
+
+ private String getSignature(DexAnnotationSet annotations) {
+ DexValueArray value =
+ (DexValueArray)
+ getSystemAnnotationValue(annotations, application.dexItemFactory.annotationSignature);
+ if (value == null) {
+ return null;
+ }
+ DexValue[] parts = value.getValues();
+ StringBuilder res = new StringBuilder();
+ for (DexValue part : parts) {
+ res.append(((DexValueString) part).getValue().toString());
+ }
+ return res.toString();
+ }
+
+ private ImmutableMap<DexString, DexValue> getAnnotationDefaults(DexAnnotationSet annotations) {
+ DexValueAnnotation value =
+ (DexValueAnnotation)
+ getSystemAnnotationValue(annotations, application.dexItemFactory.annotationDefault);
+ if (value == null) {
+ return ImmutableMap.of();
+ }
+ DexEncodedAnnotation annotation = value.value;
+ Builder<DexString, DexValue> builder = ImmutableMap.builder();
+ for (DexAnnotationElement element : annotation.elements) {
+ builder.put(element.name, element.value);
+ }
+ return builder.build();
+ }
+
+ private String[] getExceptions(DexAnnotationSet annotations) {
+ DexValueArray value =
+ (DexValueArray)
+ getSystemAnnotationValue(annotations, application.dexItemFactory.annotationThrows);
+ if (value == null) {
+ return null;
+ }
+ DexValue[] values = value.getValues();
+ String[] res = new String[values.length];
+ for (int i = 0; i < values.length; i++) {
+ res[i] = ((DexValueType) values[i]).value.getInternalName();
+ }
+ return res;
+ }
+
private Object getStaticValue(DexEncodedField field) {
if (!field.accessFlags.isStatic() || field.staticValue == null) {
return null;
@@ -143,22 +233,110 @@
int access = field.accessFlags.getAsCfAccessFlags();
String name = field.field.name.toString();
String desc = field.field.type.toDescriptorString();
- String signature = null; // TODO(zerny): Support generic signatures.
+ String signature = getSignature(field.annotations);
Object value = getStaticValue(field);
- writer.visitField(access, name, desc, signature, value);
- // TODO(zerny): Add annotations to the field.
+ FieldVisitor visitor = writer.visitField(access, name, desc, signature, value);
+ writeAnnotations(visitor::visitAnnotation, field.annotations.annotations);
+ visitor.visitEnd();
}
- private void writeMethod(DexEncodedMethod method, ClassWriter writer) {
+ private void writeMethod(
+ DexEncodedMethod method, ClassWriter writer, ImmutableMap<DexString, DexValue> defaults) {
int access = method.accessFlags.getAsCfAccessFlags();
String name = method.method.name.toString();
String desc = method.descriptor();
- String signature = null; // TODO(zerny): Support generic signatures.
- String[] exceptions = null;
+ String signature = getSignature(method.annotations);
+ String[] exceptions = getExceptions(method.annotations);
MethodVisitor visitor = writer.visitMethod(access, name, desc, signature, exceptions);
+ if (defaults.containsKey(method.method.name)) {
+ AnnotationVisitor defaultVisitor = visitor.visitAnnotationDefault();
+ if (defaultVisitor != null) {
+ writeAnnotationElement(defaultVisitor, null, defaults.get(method.method.name));
+ defaultVisitor.visitEnd();
+ }
+ }
+ writeAnnotations(visitor::visitAnnotation, method.annotations.annotations);
+ for (int i = 0; i < method.parameterAnnotations.values.length; i++) {
+ final int iFinal = i;
+ writeAnnotations(
+ (d, vis) -> visitor.visitParameterAnnotation(iFinal, d, vis),
+ method.parameterAnnotations.values[i].annotations);
+ }
if (!method.accessFlags.isAbstract() && !method.accessFlags.isNative()) {
writeCode(method.getCode(), visitor);
}
+ visitor.visitEnd();
+ }
+
+ private interface AnnotationConsumer {
+ AnnotationVisitor visit(String desc, boolean visible);
+ }
+
+ private void writeAnnotations(AnnotationConsumer visitor, DexAnnotation[] annotations) {
+ for (DexAnnotation dexAnnotation : annotations) {
+ if (dexAnnotation.visibility == DexAnnotation.VISIBILITY_SYSTEM) {
+ // Annotations with VISIBILITY_SYSTEM are not annotations in CF, but are special
+ // annotations in Dex, i.e. default, enclosing class, enclosing method, member classes,
+ // signature, throws.
+ continue;
+ }
+ AnnotationVisitor v =
+ visitor.visit(
+ dexAnnotation.annotation.type.toDescriptorString(),
+ dexAnnotation.visibility == DexAnnotation.VISIBILITY_RUNTIME);
+ if (v != null) {
+ writeAnnotation(v, dexAnnotation.annotation);
+ v.visitEnd();
+ }
+ }
+ }
+
+ private void writeAnnotation(AnnotationVisitor v, DexEncodedAnnotation annotation) {
+ for (DexAnnotationElement element : annotation.elements) {
+ writeAnnotationElement(v, element.name.toString(), element.value);
+ }
+ }
+
+ private void writeAnnotationElement(AnnotationVisitor visitor, String name, DexValue value) {
+ if (value instanceof DexValueAnnotation) {
+ DexValueAnnotation valueAnnotation = (DexValueAnnotation) value;
+ AnnotationVisitor innerVisitor =
+ visitor.visitAnnotation(name, valueAnnotation.value.type.toDescriptorString());
+ if (innerVisitor != null) {
+ writeAnnotation(innerVisitor, valueAnnotation.value);
+ innerVisitor.visitEnd();
+ }
+ } else if (value instanceof DexValueArray) {
+ DexValue[] values = ((DexValueArray) value).getValues();
+ AnnotationVisitor innerVisitor = visitor.visitArray(name);
+ if (innerVisitor != null) {
+ for (DexValue arrayValue : values) {
+ writeAnnotationElement(innerVisitor, null, arrayValue);
+ }
+ innerVisitor.visitEnd();
+ }
+ } else if (value instanceof DexValueEnum) {
+ DexValueEnum en = (DexValueEnum) value;
+ visitor.visitEnum(name, en.value.type.toDescriptorString(), en.value.name.toString());
+ } else if (value instanceof DexValueField) {
+ throw new Unreachable("writeAnnotationElement of DexValueField");
+ } else if (value instanceof DexValueMethod) {
+ throw new Unreachable("writeAnnotationElement of DexValueMethod");
+ } else if (value instanceof DexValueMethodHandle) {
+ throw new Unreachable("writeAnnotationElement of DexValueMethodHandle");
+ } else if (value instanceof DexValueMethodType) {
+ throw new Unreachable("writeAnnotationElement of DexValueMethodType");
+ } else if (value instanceof DexValueString) {
+ DexValueString str = (DexValueString) value;
+ visitor.visit(name, str.getValue().toString());
+ } else if (value instanceof DexValueType) {
+ DexValueType ty = (DexValueType) value;
+ visitor.visit(name, Type.getType(ty.value.toDescriptorString()));
+ } else if (value instanceof UnknownDexValue) {
+ throw new Unreachable("writeAnnotationElement of UnknownDexValue");
+ } else {
+ visitor.visit(name, value.getBoxedValue());
+ }
}
private void writeCode(Code code, MethodVisitor visitor) {
diff --git a/src/test/java/com/android/tools/r8/cf/AnnotationTest.java b/src/test/java/com/android/tools/r8/cf/AnnotationTest.java
new file mode 100644
index 0000000..d586bb2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/AnnotationTest.java
@@ -0,0 +1,46 @@
+// 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.cf;
+
+import java.lang.annotation.Annotation;
+
+public class AnnotationTest {
+ // @Deprecated is a runtime-visible annotation.
+ @Deprecated public static boolean foo = true;
+
+ @Deprecated
+ public static boolean bar() {
+ return true;
+ }
+
+ public static void main(String[] args) {
+ try {
+ testField();
+ testMethod();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static void testField() throws Exception {
+ checkDeprecated("field 'foo'", AnnotationTest.class.getDeclaredField("foo").getAnnotations());
+ }
+
+ private static void testMethod() throws Exception {
+ checkDeprecated("method 'bar'", AnnotationTest.class.getMethod("bar").getAnnotations());
+ }
+
+ private static void checkDeprecated(String what, Annotation[] annotations) {
+ Annotation n;
+ try {
+ n = annotations[0];
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw new RuntimeException(what + ": No annotations, expected @Deprecated", e);
+ }
+ if (!(n instanceof Deprecated)) {
+ throw new RuntimeException(what + ": Expected @Deprecated, got: " + n);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/AnnotationTestRunner.java b/src/test/java/com/android/tools/r8/cf/AnnotationTestRunner.java
new file mode 100644
index 0000000..addfa58
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/AnnotationTestRunner.java
@@ -0,0 +1,40 @@
+// 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.cf;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ClassFileConsumer.DirectoryConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.origin.Origin;
+import java.nio.file.Path;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class AnnotationTestRunner {
+ static final Class CLASS = AnnotationTest.class;
+ @Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+ @Test
+ public void test() throws Exception {
+ ProcessResult runInput = ToolHelper
+ .runJava(ToolHelper.getClassPathForTests(), CLASS.getCanonicalName());
+ assertEquals(0, runInput.exitCode);
+ Path out = temp.getRoot().toPath();
+ R8.run(
+ R8Command.builder()
+ .setMode(CompilationMode.DEBUG)
+ .addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown())
+ .addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
+ .setProgramConsumer(new DirectoryConsumer(out))
+ .build());
+ ProcessResult runOutput = ToolHelper.runJava(out, CLASS.getCanonicalName());
+ assertEquals(runInput.toString(), runOutput.toString());
+ }
+}