blob: f907d94a21ba2329130e118ab2a9a88e54f7e6a6 [file] [log] [blame]
// Copyright (c) 2017, 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.jar;
import static org.objectweb.asm.Opcodes.ASM6;
import com.android.tools.r8.OutputSink;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexApplication;
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.utils.InternalOptions;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collections;
import java.util.concurrent.ExecutorService;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.util.CheckClassAdapter;
import org.objectweb.asm.util.Textifier;
import org.objectweb.asm.util.TraceMethodVisitor;
public class CfApplicationWriter {
private final DexApplication application;
private final InternalOptions options;
public CfApplicationWriter(DexApplication application, InternalOptions options) {
this.application = application;
this.options = options;
}
public void write(OutputSink outputSink, ExecutorService executor) throws IOException {
application.timing.begin("CfApplicationWriter.write");
try {
writeApplication(outputSink, executor);
} finally {
application.timing.end();
}
}
private void writeApplication(OutputSink outputSink, ExecutorService executor)
throws IOException {
for (DexProgramClass clazz : application.classes()) {
if (clazz.getSynthesizedFrom().isEmpty()) {
writeClass(clazz, outputSink);
} else {
throw new Unimplemented("No support for synthetics in the Java bytecode backend.");
}
}
}
// TODO(zerny): Implement stack-map computation to support Java 1.6 and above.
private int downgrade(int version) {
return version > 49 ? 49 : version;
}
private void writeClass(DexProgramClass clazz, OutputSink outputSink) throws IOException {
ClassWriter writer = new ClassWriter(0);
writer.visitSource(clazz.sourceFile != null ? clazz.sourceFile.toString() : null, null);
int version = downgrade(clazz.getClassFileVersion());
int access = clazz.accessFlags.getAsCfAccessFlags();
String desc = clazz.type.toDescriptorString();
String name = clazz.type.getInternalName();
String signature = null; // TODO(zerny): Support generic signatures.
String superName =
clazz.type == options.itemFactory.objectType ? null : clazz.superType.getInternalName();
String[] interfaces = new String[clazz.interfaces.values.length];
for (int i = 0; i < clazz.interfaces.values.length; i++) {
interfaces[i] = clazz.interfaces.values[i].getInternalName();
}
writer.visit(version, access, name, signature, superName, interfaces);
for (DexEncodedField field : clazz.staticFields()) {
writeField(field, writer);
}
for (DexEncodedField field : clazz.instanceFields()) {
writeField(field, writer);
}
for (DexEncodedMethod method : clazz.directMethods()) {
writeMethod(method, writer);
}
for (DexEncodedMethod method : clazz.virtualMethods()) {
writeMethod(method, writer);
}
writer.visitEnd();
byte[] result = writer.toByteArray();
assert verifyCf(result);
outputSink.writeClassFile(result, Collections.singleton(desc), desc);
}
private Object getStaticValue(DexEncodedField field) {
if (!field.accessFlags.isStatic() || field.staticValue == null) {
return null;
}
return field.staticValue.asAsmEncodedObject();
}
private void writeField(DexEncodedField field, ClassWriter writer) {
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.
Object value = getStaticValue(field);
writer.visitField(access, name, desc, signature, value);
// TODO(zerny): Add annotations to the field.
}
private void writeMethod(DexEncodedMethod method, ClassWriter writer) {
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;
MethodVisitor visitor = writer.visitMethod(access, name, desc, signature, exceptions);
if (!method.accessFlags.isAbstract()) {
writeCode(method.getCode(), visitor);
}
}
private void writeCode(Code code, MethodVisitor visitor) {
if (code.isJarCode()) {
code.asJarCode().writeTo(visitor);
} else {
assert code.isCfCode();
code.asCfCode().write(visitor);
}
}
private String printCf(byte[] result) {
ClassReader reader = new ClassReader(result);
ClassNode node = new ClassNode(ASM6);
reader.accept(node, ASM6);
StringWriter writer = new StringWriter();
for (MethodNode method : node.methods) {
writer.append(method.name).append(method.desc).append('\n');
TraceMethodVisitor visitor = new TraceMethodVisitor(new Textifier());
method.accept(visitor);
visitor.p.print(new PrintWriter(writer));
writer.append('\n');
}
return writer.toString();
}
private static boolean verifyCf(byte[] result) {
ClassReader reader = new ClassReader(result);
PrintWriter pw = new PrintWriter(System.out);
CheckClassAdapter.verify(reader, false, pw);
return true;
}
}