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