blob: 74d7686b319a5d16f507d78c121a65a958a0187c [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 com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
import com.android.tools.r8.ByteDataView;
import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.cf.CfVersion;
import com.android.tools.r8.dex.ApplicationWriter;
import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.errors.CodeSizeOverflowDiagnostic;
import com.android.tools.r8.errors.ConstantPoolOverflowDiagnostic;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
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.DexField;
import com.android.tools.r8.graph.DexMethod;
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.DexValueInt;
import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.graph.NestMemberClassAttribute;
import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.naming.ProguardMapSupplier;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.synthesis.SyntheticNaming;
import com.android.tools.r8.utils.AsmUtils;
import com.android.tools.r8.utils.ComparatorUtils;
import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.PredicateUtils;
import com.android.tools.r8.utils.structural.Ordered;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassTooLargeException;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodTooLargeException;
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;
import org.objectweb.asm.util.Textifier;
import org.objectweb.asm.util.TraceMethodVisitor;
public class CfApplicationWriter {
private static final boolean RUN_VERIFIER = false;
private static final boolean PRINT_CF = false;
// First item inserted into the constant pool is the marker string which generates an UTF8 to
// pool index #1 and a String entry to #2, referencing #1.
public static final int MARKER_STRING_CONSTANT_POOL_INDEX = 2;
private final DexApplication application;
private final AppView<?> appView;
private final GraphLens graphLens;
private final NamingLens namingLens;
private final InternalOptions options;
private final Marker marker;
private final Predicate<DexType> isTypeMissing;
public final ProguardMapSupplier proguardMapSupplier;
private static final CfVersion MIN_VERSION_FOR_COMPILER_GENERATED_CODE = CfVersion.V1_6;
public CfApplicationWriter(
AppView<?> appView,
Marker marker,
GraphLens graphLens,
NamingLens namingLens,
ProguardMapSupplier proguardMapSupplier) {
this.application = appView.appInfo().app();
this.appView = appView;
this.graphLens = graphLens;
this.namingLens = namingLens;
this.options = appView.options();
assert marker != null;
this.marker = marker;
this.proguardMapSupplier = proguardMapSupplier;
this.isTypeMissing =
PredicateUtils.isNull(appView.appInfo()::definitionForWithoutExistenceAssert);
}
public void write(ClassFileConsumer consumer) {
application.timing.begin("CfApplicationWriter.write");
try {
writeApplication(consumer);
} finally {
application.timing.end();
}
}
private void writeApplication(ClassFileConsumer consumer) {
if (proguardMapSupplier != null && options.proguardMapConsumer != null) {
marker.setPgMapId(proguardMapSupplier.writeProguardMap().get());
}
Optional<String> markerString =
marker.isRelocator() ? Optional.empty() : Optional.of(marker.toString());
LensCodeRewriterUtils rewriter = new LensCodeRewriterUtils(appView);
for (DexProgramClass clazz : application.classes()) {
assert SyntheticNaming.verifyNotInternalSynthetic(clazz.getType());
try {
writeClass(clazz, consumer, rewriter, markerString);
} catch (ClassTooLargeException e) {
throw appView
.options()
.reporter
.fatalError(
new ConstantPoolOverflowDiagnostic(
clazz.getOrigin(),
Reference.classFromBinaryName(e.getClassName()),
e.getConstantPoolCount()));
} catch (MethodTooLargeException e) {
throw appView
.options()
.reporter
.fatalError(
new CodeSizeOverflowDiagnostic(
clazz.getOrigin(),
Reference.methodFromDescriptor(
Reference.classFromBinaryName(e.getClassName()).getDescriptor(),
e.getMethodName(),
e.getDescriptor()),
e.getCodeSize()));
}
}
ApplicationWriter.supplyAdditionalConsumers(
application, appView, graphLens, namingLens, options);
}
private void writeClass(
DexProgramClass clazz,
ClassFileConsumer consumer,
LensCodeRewriterUtils rewriter,
Optional<String> markerString) {
ClassWriter writer = new ClassWriter(0);
if (markerString.isPresent()) {
int markerStringPoolIndex = writer.newConst(markerString.get());
assert markerStringPoolIndex == MARKER_STRING_CONSTANT_POOL_INDEX;
}
String sourceDebug = getSourceDebugExtension(clazz.annotations());
writer.visitSource(clazz.sourceFile != null ? clazz.sourceFile.toString() : null, sourceDebug);
CfVersion version = getClassFileVersion(clazz);
if (version.isGreaterThanOrEqualTo(CfVersion.V1_8)) {
// JDK8 and after ignore ACC_SUPER so unset it.
clazz.accessFlags.unsetSuper();
} else {
// In all other cases set the super bit as D8/R8 do not support targeting pre 1.0.2 JDKs.
if (!clazz.accessFlags.isInterface()) {
clazz.accessFlags.setSuper();
}
}
int access =
options.testing.allowInvalidCfAccessFlags
? clazz.accessFlags.materialize()
: 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 = clazz.getClassSignature().toRenamedString(namingLens, isTypeMissing);
String superName =
clazz.type == options.itemFactory.objectType
? null
: namingLens.lookupInternalName(clazz.superType);
String[] interfaces = new String[clazz.interfaces.values.length];
for (int i = 0; i < clazz.interfaces.values.length; i++) {
interfaces[i] = namingLens.lookupInternalName(clazz.interfaces.values[i]);
}
assert SyntheticNaming.verifyNotInternalSynthetic(name);
writer.visit(version.raw(), access, name, signature, superName, interfaces);
appView.getSyntheticItems().writeAttributeIfIntermediateSyntheticClass(writer, clazz, appView);
writeAnnotations(writer::visitAnnotation, clazz.annotations().annotations);
ImmutableMap<DexString, DexValue> defaults = getAnnotationDefaults(clazz.annotations());
if (clazz.getEnclosingMethodAttribute() != null) {
clazz.getEnclosingMethodAttribute().write(writer, namingLens);
}
if (clazz.getNestHostClassAttribute() != null) {
clazz.getNestHostClassAttribute().write(writer, namingLens);
}
for (NestMemberClassAttribute entry : clazz.getNestMembersClassAttributes()) {
entry.write(writer, namingLens);
assert clazz.getNestHostClassAttribute() == null
: "A nest host cannot also be a nest member.";
}
if (clazz.isRecord()) {
// TODO(b/169645628): Strip record components if not kept.
for (DexEncodedField instanceField : clazz.instanceFields()) {
String componentName = namingLens.lookupName(instanceField.getReference()).toString();
String componentDescriptor =
namingLens.lookupDescriptor(instanceField.getReference().type).toString();
String componentSignature =
instanceField.getGenericSignature().toRenamedString(namingLens, isTypeMissing);
writer.visitRecordComponent(componentName, componentDescriptor, componentSignature);
}
}
for (InnerClassAttribute entry : clazz.getInnerClasses()) {
entry.write(writer, namingLens, options);
}
for (DexEncodedField field : clazz.staticFields()) {
writeField(field, writer);
}
for (DexEncodedField field : clazz.instanceFields()) {
writeField(field, writer);
}
if (options.desugarSpecificOptions().sortMethodsOnCfOutput) {
List<ProgramMethod> programMethodSorted = new ArrayList<>();
clazz.forEachProgramMethod(programMethodSorted::add);
programMethodSorted.sort(this::compareMethodsThroughLens);
programMethodSorted.forEach(
method -> writeMethod(method, version, rewriter, writer, defaults));
} else {
clazz.forEachProgramMethod(
method -> writeMethod(method, version, rewriter, writer, defaults));
}
writer.visitEnd();
byte[] result = writer.toByteArray();
if (PRINT_CF) {
System.out.print(printCf(result));
System.out.flush();
}
if (RUN_VERIFIER) {
// Generally, this will fail with ClassNotFoundException,
// so don't assert that verifyCf() returns true.
verifyCf(result);
}
ExceptionUtils.withConsumeResourceHandler(
options.reporter, handler -> consumer.accept(ByteDataView.of(result), desc, handler));
}
private int compareTypesThroughLens(DexType a, DexType b) {
return namingLens.lookupDescriptor(a).compareTo(namingLens.lookupDescriptor(b));
}
private DexString returnTypeThroughLens(DexMethod method) {
return namingLens.lookupDescriptor(method.getReturnType());
}
private int compareMethodsThroughLens(ProgramMethod a, ProgramMethod b) {
// When writing class files, methods are only compared within the same class.
assert a.getHolder().equals(b.getHolder());
return Comparator.comparing(this::returnTypeThroughLens)
.thenComparing(DexMethod::getName)
// .thenComparingInt(m -> m.getProto().getArity()) // Done in arrayComp below.
.thenComparing(
m -> m.getProto().parameters.values,
ComparatorUtils.arrayComparator(this::compareTypesThroughLens))
.compare(a.getReference(), b.getReference());
}
private CfVersion getClassFileVersion(DexEncodedMethod method) {
if (!method.hasClassFileVersion()) {
// In this case bridges have been introduced for the Cf back-end,
// which do not have class file version.
assert options.isDesugaredLibraryCompilation() || options.cfToCfDesugar
: "Expected class file version for " + method.getReference().toSourceString();
assert MIN_VERSION_FOR_COMPILER_GENERATED_CODE.isLessThan(
options.classFileVersionAfterDesugaring(InternalOptions.SUPPORTED_CF_VERSION));
// Any desugaring rewrites which cannot meet the default class file version after
// desugaring must upgrade the class file version during desugaring.
return options.cfToCfDesugar
? options.classFileVersionAfterDesugaring(InternalOptions.SUPPORTED_CF_VERSION)
: MIN_VERSION_FOR_COMPILER_GENERATED_CODE;
}
return method.getClassFileVersion();
}
private CfVersion getClassFileVersion(DexProgramClass clazz) {
CfVersion version =
clazz.hasClassFileVersion()
? clazz.getInitialClassFileVersion()
: MIN_VERSION_FOR_COMPILER_GENERATED_CODE;
for (DexEncodedMethod method : clazz.directMethods()) {
version = Ordered.max(version, getClassFileVersion(method));
}
for (DexEncodedMethod method : clazz.virtualMethods()) {
version = Ordered.max(version, getClassFileVersion(method));
}
return version;
}
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 getSourceDebugExtension(DexAnnotationSet annotations) {
DexValue debugExtensions =
getSystemAnnotationValue(
annotations, application.dexItemFactory.annotationSourceDebugExtension);
if (debugExtensions == null) {
return null;
}
return debugExtensions.asDexValueString().getValue().toString();
}
private ImmutableMap<DexString, DexValue> getAnnotationDefaults(DexAnnotationSet annotations) {
DexValue value =
getSystemAnnotationValue(annotations, application.dexItemFactory.annotationDefault);
if (value == null) {
return ImmutableMap.of();
}
DexEncodedAnnotation annotation = value.asDexValueAnnotation().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) {
DexValue value =
getSystemAnnotationValue(annotations, application.dexItemFactory.annotationThrows);
if (value == null) {
return null;
}
DexValue[] values = value.asDexValueArray().getValues();
String[] res = new String[values.length];
for (int i = 0; i < values.length; i++) {
res[i] = namingLens.lookupInternalName(values[i].asDexValueType().value);
}
return res;
}
private Object getStaticValue(DexEncodedField field) {
if (!field.accessFlags.isStatic() || !field.hasExplicitStaticValue()) {
return null;
}
return field.getStaticValue().asAsmEncodedObject();
}
private void writeField(DexEncodedField field, ClassWriter writer) {
int access = field.accessFlags.getAsCfAccessFlags();
if (field.isDeprecated()) {
access = AsmUtils.withDeprecated(access);
}
String name = namingLens.lookupName(field.getReference()).toString();
String desc = namingLens.lookupDescriptor(field.getReference().type).toString();
String signature = field.getGenericSignature().toRenamedString(namingLens, isTypeMissing);
Object value = getStaticValue(field);
FieldVisitor visitor = writer.visitField(access, name, desc, signature, value);
writeAnnotations(visitor::visitAnnotation, field.annotations().annotations);
visitor.visitEnd();
}
private void writeMethod(
ProgramMethod method,
CfVersion classFileVersion,
LensCodeRewriterUtils rewriter,
ClassWriter writer,
ImmutableMap<DexString, DexValue> defaults) {
NamingLens namingLens = this.namingLens;
// For "pass through" classes which has already been library desugared use the identity lens.
if (appView.isAlreadyLibraryDesugared(method.getHolder())) {
namingLens = NamingLens.getIdentityLens();
}
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 =
method.getDefinition().getGenericSignature().toRenamedString(namingLens, isTypeMissing);
String[] exceptions = getExceptions(definition.annotations());
MethodVisitor visitor = writer.visitMethod(access, name, desc, signature, exceptions);
if (defaults.containsKey(definition.getName())) {
AnnotationVisitor defaultVisitor = visitor.visitAnnotationDefault();
if (defaultVisitor != null) {
writeAnnotationElement(defaultVisitor, null, defaults.get(definition.getName()));
defaultVisitor.visitEnd();
}
}
writeMethodParametersAnnotation(visitor, definition.annotations().annotations);
writeAnnotations(visitor::visitAnnotation, definition.annotations().annotations);
writeParameterAnnotations(visitor, definition.parameterAnnotationsList);
if (!definition.shouldNotHaveCode()) {
writeCode(method, classFileVersion, namingLens, rewriter, visitor);
}
visitor.visitEnd();
}
private void writeMethodParametersAnnotation(MethodVisitor visitor, DexAnnotation[] annotations) {
for (DexAnnotation annotation : annotations) {
if (annotation.annotation.type == appView.dexItemFactory().annotationMethodParameters) {
assert annotation.visibility == DexAnnotation.VISIBILITY_SYSTEM;
assert annotation.annotation.elements.length == 2;
assert annotation.annotation.elements[0].name.toString().equals("names");
assert annotation.annotation.elements[1].name.toString().equals("accessFlags");
DexValueArray names = annotation.annotation.elements[0].value.asDexValueArray();
DexValueArray accessFlags = annotation.annotation.elements[1].value.asDexValueArray();
assert names != null && accessFlags != null;
assert names.getValues().length == accessFlags.getValues().length;
for (int i = 0; i < names.getValues().length; i++) {
DexValueString name = names.getValues()[i].asDexValueString();
DexValueInt access = accessFlags.getValues()[i].asDexValueInt();
visitor.visitParameter(name.value.toString(), access.value);
}
}
}
}
private void writeParameterAnnotations(
MethodVisitor visitor, ParameterAnnotationsList parameterAnnotations) {
// TODO(113565942): We currently assume that the annotable parameter count
// it the same for visible and invisible annotations. That doesn't actually
// seem to be the case in the class file format.
visitor.visitAnnotableParameterCount(
parameterAnnotations.getAnnotableParameterCount(), true);
visitor.visitAnnotableParameterCount(
parameterAnnotations.getAnnotableParameterCount(), false);
for (int i = 0; i < parameterAnnotations.size(); i++) {
int iFinal = i;
writeAnnotations(
(d, vis) -> visitor.visitParameterAnnotation(iFinal, d, vis),
parameterAnnotations.get(i).annotations);
}
}
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(
namingLens.lookupDescriptor(dexAnnotation.annotation.type).toString(),
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) {
switch (value.getValueKind()) {
case ANNOTATION:
{
DexValueAnnotation valueAnnotation = value.asDexValueAnnotation();
AnnotationVisitor innerVisitor =
visitor.visitAnnotation(
name, namingLens.lookupDescriptor(valueAnnotation.value.type).toString());
if (innerVisitor != null) {
writeAnnotation(innerVisitor, valueAnnotation.value);
innerVisitor.visitEnd();
}
}
break;
case ARRAY:
{
DexValue[] values = value.asDexValueArray().getValues();
AnnotationVisitor innerVisitor = visitor.visitArray(name);
if (innerVisitor != null) {
for (DexValue elementValue : values) {
writeAnnotationElement(innerVisitor, null, elementValue);
}
innerVisitor.visitEnd();
}
}
break;
case ENUM:
DexField enumField = value.asDexValueEnum().getValue();
visitor.visitEnum(
name,
namingLens.lookupDescriptor(enumField.getType()).toString(),
namingLens.lookupName(enumField).toString());
break;
case FIELD:
throw new Unreachable("writeAnnotationElement of DexValueField");
case METHOD:
throw new Unreachable("writeAnnotationElement of DexValueMethod");
case METHOD_HANDLE:
throw new Unreachable("writeAnnotationElement of DexValueMethodHandle");
case METHOD_TYPE:
throw new Unreachable("writeAnnotationElement of DexValueMethodType");
case STRING:
visitor.visit(name, value.asDexValueString().getValue().toString());
break;
case TYPE:
visitor.visit(
name,
Type.getType(namingLens.lookupDescriptor(value.asDexValueType().value).toString()));
break;
default:
visitor.visit(name, value.getBoxedValue());
break;
}
}
private void writeCode(
ProgramMethod method,
CfVersion classFileVersion,
NamingLens namingLens,
LensCodeRewriterUtils rewriter,
MethodVisitor visitor) {
CfCode code = method.getDefinition().getCode().asCfCode();
code.write(method, classFileVersion, appView, namingLens, rewriter, visitor);
}
public static String printCf(byte[] result) {
ClassReader reader = new ClassReader(result);
ClassNode node = new ClassNode(ASM_VERSION);
reader.accept(node, ASM_VERSION);
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 void verifyCf(byte[] result) {
ClassReader reader = new ClassReader(result);
PrintWriter pw = new PrintWriter(System.out);
CheckClassAdapter.verify(reader, false, pw);
}
}