Consistently use same naming lens for each class
This introduces a new CfApplicationClassWriter responsible for a writing a single class. This class has a NamingLens field that should be used for all writing performed by that instance.
Bug: b/393202425
Change-Id: Ibebceba6da39c6ffe98392736ef6ff256d3417b6
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 a515a46..389f92d 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -34,7 +34,7 @@
import com.android.tools.r8.graph.GenericSignature.ClassSignature;
import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
-import com.android.tools.r8.jar.CfApplicationWriter;
+import com.android.tools.r8.jar.CfApplicationClassWriter;
import com.android.tools.r8.keepanno.asm.KeepEdgeReader;
import com.android.tools.r8.keepanno.ast.ParsingContext.AnnotationParsingContext;
import com.android.tools.r8.keepanno.ast.ParsingContext.ClassParsingContext;
@@ -129,12 +129,12 @@
parsingOptions);
// Read marker.
- if (reader.getItemCount() > CfApplicationWriter.MARKER_STRING_CONSTANT_POOL_INDEX
- && reader.getItem(CfApplicationWriter.MARKER_STRING_CONSTANT_POOL_INDEX) > 0) {
+ if (reader.getItemCount() > CfApplicationClassWriter.MARKER_STRING_CONSTANT_POOL_INDEX
+ && reader.getItem(CfApplicationClassWriter.MARKER_STRING_CONSTANT_POOL_INDEX) > 0) {
try {
Object maybeMarker =
reader.readConst(
- CfApplicationWriter.MARKER_STRING_CONSTANT_POOL_INDEX,
+ CfApplicationClassWriter.MARKER_STRING_CONSTANT_POOL_INDEX,
new char[reader.getMaxStringLength()]);
if (maybeMarker instanceof String) {
application.getFactory().createMarkerString((String) maybeMarker);
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationClassWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationClassWriter.java
new file mode 100644
index 0000000..2399f9f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationClassWriter.java
@@ -0,0 +1,601 @@
+// Copyright (c) 2025, 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.SourceFileEnvironment;
+import com.android.tools.r8.cf.CfVersion;
+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.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.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.DexItemFactory;
+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.DexTypeAnnotation;
+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.InnerClassAttribute;
+import com.android.tools.r8.graph.NestMemberClassAttribute;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.PermittedSubclassAttribute;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.RecordComponentInfo;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.naming.NamingLens;
+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.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 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.TypePath;
+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 CfApplicationClassWriter {
+
+ // 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 static final CfVersion MIN_VERSION_FOR_COMPILER_GENERATED_CODE = CfVersion.V1_6;
+ private static final boolean PRINT_CF = false;
+ private static final boolean RUN_VERIFIER = false;
+
+ private final AppView<?> appView;
+ private final DexProgramClass clazz;
+ private final DexItemFactory factory;
+ private final NamingLens namingLens;
+ private final InternalOptions options;
+
+ CfApplicationClassWriter(AppView<?> appView, DexProgramClass clazz) {
+ this.appView = appView;
+ this.clazz = clazz;
+ this.factory = appView.dexItemFactory();
+ // For "pass through" classes which has already been library desugared use the identity lens.
+ this.namingLens =
+ appView.isAlreadyLibraryDesugared(clazz)
+ ? NamingLens.getIdentityLens()
+ : appView.getNamingLens();
+ this.options = appView.options();
+ }
+
+ private boolean isTypeMissing(DexType type) {
+ return !appView.appInfo().hasDefinitionForWithoutExistenceAssert(type);
+ }
+
+ void writeClassCatchingErrors(
+ ClassFileConsumer consumer,
+ LensCodeRewriterUtils rewriter,
+ Optional<String> markerString,
+ SourceFileEnvironment sourceFileEnvironment) {
+ assert SyntheticNaming.verifyNotInternalSynthetic(clazz.getType());
+ try {
+ writeClass(consumer, rewriter, markerString, sourceFileEnvironment);
+ } catch (ClassTooLargeException e) {
+ throw options.reporter.fatalError(
+ new ConstantPoolOverflowDiagnostic(
+ clazz.getOrigin(),
+ Reference.classFromBinaryName(e.getClassName()),
+ e.getConstantPoolCount()));
+ } catch (MethodTooLargeException e) {
+ throw options.reporter.fatalError(
+ new CodeSizeOverflowDiagnostic(
+ clazz.getOrigin(),
+ Reference.methodFromDescriptor(
+ Reference.classFromBinaryName(e.getClassName()).getDescriptor(),
+ e.getMethodName(),
+ e.getDescriptor()),
+ e.getCodeSize()));
+ }
+ }
+
+ private void writeClass(
+ ClassFileConsumer consumer,
+ LensCodeRewriterUtils rewriter,
+ Optional<String> markerString,
+ SourceFileEnvironment sourceFileEnvironment) {
+ ClassWriter writer = new ClassWriter(0);
+ if (markerString.isPresent()) {
+ int markerStringPoolIndex = writer.newConst(markerString.get());
+ assert markerStringPoolIndex == MARKER_STRING_CONSTANT_POOL_INDEX;
+ }
+ String sourceFile;
+ if (options.sourceFileProvider == null) {
+ sourceFile = clazz.sourceFile != null ? clazz.sourceFile.toString() : null;
+ } else {
+ sourceFile = options.sourceFileProvider.get(sourceFileEnvironment);
+ }
+ String sourceDebug = getSourceDebugExtension(clazz.annotations());
+ writer.visitSource(sourceFile, 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();
+ }
+ }
+ boolean allowInvalidCfAccessFlags =
+ clazz.getType().getDescriptor().endsWith(factory.createString("/package-info;"));
+ int access =
+ allowInvalidCfAccessFlags || 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, this::isTypeMissing);
+ String superName = clazz.hasSuperType() ? namingLens.lookupInternalName(clazz.superType) : null;
+ 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, writer::visitTypeAnnotation, 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.";
+ }
+
+ for (PermittedSubclassAttribute entry : clazz.getPermittedSubclassAttributes()) {
+ entry.write(writer, namingLens);
+ }
+
+ if (clazz.isRecord()) {
+ // TODO(b/274888318): Strip record components if not kept.
+ for (RecordComponentInfo info : clazz.getRecordComponents()) {
+ info.write(writer, namingLens, this::isTypeMissing, this::writeAnnotation);
+ }
+ }
+
+ for (InnerClassAttribute entry : clazz.getInnerClasses()) {
+ entry.write(writer, namingLens, options);
+ }
+
+ clazz.forEachProgramStaticField(field -> writeField(field, writer));
+ clazz.forEachProgramInstanceField(field -> 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 String getSourceDebugExtension(DexAnnotationSet annotations) {
+ DexValue debugExtensions =
+ getSystemAnnotationValue(annotations, factory.annotationSourceDebugExtension);
+ if (debugExtensions == null) {
+ return null;
+ }
+ return debugExtensions.asDexValueString().getValue().toString();
+ }
+
+ @SuppressWarnings("BadImport")
+ private ImmutableMap<DexString, DexValue> getAnnotationDefaults(DexAnnotationSet annotations) {
+ DexValue value = getSystemAnnotationValue(annotations, factory.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, NamingLens namingLens) {
+ DexValue value = getSystemAnnotationValue(annotations, factory.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(ProgramField field, ClassWriter writer) {
+ int access = field.getAccessFlags().getAsCfAccessFlags();
+ if (field.getDefinition().isDeprecated()) {
+ access = AsmUtils.withDeprecated(access);
+ }
+ String name = namingLens.lookupName(field.getReference()).toString();
+ String desc = namingLens.lookupDescriptor(field.getReference().type).toString();
+ String signature =
+ field
+ .getDefinition()
+ .getGenericSignature()
+ .toRenamedString(namingLens, this::isTypeMissing);
+ Object value = getStaticValue(field.getDefinition());
+ FieldVisitor visitor = writer.visitField(access, name, desc, signature, value);
+ writeAnnotations(
+ visitor::visitAnnotation,
+ visitor::visitTypeAnnotation,
+ field.getAnnotations().getAnnotations());
+ visitor.visitEnd();
+ }
+
+ private void writeMethod(
+ ProgramMethod method,
+ CfVersion classFileVersion,
+ LensCodeRewriterUtils rewriter,
+ ClassWriter writer,
+ 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 =
+ method
+ .getDefinition()
+ .getGenericSignature()
+ .toRenamedString(namingLens, this::isTypeMissing);
+ String[] exceptions = getExceptions(definition.annotations(), namingLens);
+ 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,
+ visitor::visitTypeAnnotation,
+ definition.annotations().annotations);
+ writeParameterAnnotations(visitor, definition.parameterAnnotationsList);
+ if (!definition.shouldNotHaveCode()) {
+ writeCode(method, classFileVersion, namingLens, rewriter, visitor);
+ }
+ visitor.visitEnd();
+ }
+
+ @SuppressWarnings("ReferenceEquality")
+ private void writeMethodParametersAnnotation(MethodVisitor visitor, DexAnnotation[] annotations) {
+ for (DexAnnotation annotation : annotations) {
+ if (annotation.annotation.type == factory.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();
+ String nameString = name != null ? name.value.toString() : null;
+ visitor.visitParameter(nameString, 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),
+ (typeRef, typePath, desc, visible) -> {
+ throw new Unreachable("Type annotations are not placed on parameters");
+ },
+ parameterAnnotations.get(i).annotations);
+ }
+ }
+
+ private void writeAnnotations(
+ AnnotationConsumer visitor,
+ TypeAnnotationConsumer typeAnnotationVisitor,
+ 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;
+ }
+ String desc = namingLens.lookupDescriptor(dexAnnotation.annotation.type).toString();
+ boolean visible = dexAnnotation.visibility == DexAnnotation.VISIBILITY_RUNTIME;
+ DexTypeAnnotation dexTypeAnnotation = dexAnnotation.asTypeAnnotation();
+ AnnotationVisitor v =
+ dexTypeAnnotation == null
+ ? visitor.visit(desc, visible)
+ : typeAnnotationVisitor.visit(
+ dexTypeAnnotation.getTypeRef(), dexTypeAnnotation.getTypePath(), desc, visible);
+ 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);
+ }
+ }
+
+ @SuppressWarnings("ReferenceEquality")
+ 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();
+ // This must not be renamed, as the Java runtime will use Enum.valueOf to find the enum's
+ // referenced in annotations. See b/236691999 for details.
+ assert namingLens.lookupName(enumField) == enumField.name
+ || System.getProperty("com.android.tools.r8.tracereferences.obfuscateAllEnums")
+ != null
+ : "Enum field " + enumField.name + " renamed to " + namingLens.lookupName(enumField);
+ visitor.visitEnum(
+ name,
+ namingLens.lookupDescriptor(enumField.getType()).toString(),
+ enumField.name.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) {
+ Code code = method.getDefinition().getCode();
+ assert code.isCfWritableCode();
+ assert code.estimatedDexCodeSizeUpperBoundInBytes() > 0;
+ if (!code.isCfWritableCode()) {
+ // This should never happen (see assertion above), but desugaring bugs may lead the
+ // CfApplicationWriter to try to write invalid code and we need the better error message.
+ throw new Unreachable(
+ "The CfApplicationWriter cannot write non cf writable code "
+ + code.getClass().getCanonicalName()
+ + " for method "
+ + method.getReference().toSourceString());
+ }
+ code.asCfWritableCode()
+ .writeCf(method, classFileVersion, appView, namingLens, rewriter, visitor);
+ }
+
+ 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.getLibraryDesugaringOptions().isDesugaredLibraryCompilation()
+ || options.isDesugaring()
+ : "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.isDesugaring()
+ ? 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 interface AnnotationConsumer {
+ AnnotationVisitor visit(String desc, boolean visible);
+ }
+
+ private interface TypeAnnotationConsumer {
+ AnnotationVisitor visit(int typeRef, TypePath typePath, String desc, boolean visible);
+ }
+
+ 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();
+ }
+
+ @SuppressWarnings("DefaultCharset")
+ private static void verifyCf(byte[] result) {
+ ClassReader reader = new ClassReader(result);
+ PrintWriter pw = new PrintWriter(System.out);
+ CheckClassAdapter.verify(reader, false, pw);
+ }
+}
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 dc57f39..70b8e56 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -3,115 +3,41 @@
// 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 static com.android.tools.r8.utils.positions.LineNumberOptimizer.runAndWriteMap;
-import com.android.tools.r8.ByteDataView;
import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.SourceFileEnvironment;
-import com.android.tools.r8.cf.CfVersion;
import com.android.tools.r8.debuginfo.DebugRepresentation;
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.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.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.DexTypeAnnotation;
-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.InnerClassAttribute;
-import com.android.tools.r8.graph.NestMemberClassAttribute;
-import com.android.tools.r8.graph.ParameterAnnotationsList;
-import com.android.tools.r8.graph.PermittedSubclassAttribute;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.RecordComponentInfo;
import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapId;
-import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.synthesis.SyntheticNaming;
import com.android.tools.r8.utils.AndroidApp;
-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.InternalGlobalSyntheticsProgramConsumer.InternalGlobalSyntheticsCfConsumer;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OriginalSourceFiles;
-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.Collection;
import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;
-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.TypePath;
-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 InternalOptions options;
private final Optional<Marker> marker;
- private final Predicate<DexType> isTypeMissing;
-
- private static final CfVersion MIN_VERSION_FOR_COMPILER_GENERATED_CODE = CfVersion.V1_6;
public CfApplicationWriter(AppView<?> appView, Marker marker) {
this.application = appView.appInfo().app();
this.appView = appView;
this.options = appView.options();
this.marker = Optional.ofNullable(marker);
- this.isTypeMissing =
- PredicateUtils.isNull(appView.appInfo()::definitionForWithoutExistenceAssert);
- }
-
- private NamingLens getNamingLens() {
- return appView.getNamingLens();
}
public void write(ClassFileConsumer consumer, ExecutorService executorService) {
@@ -179,536 +105,19 @@
}
}
for (DexProgramClass clazz : classes) {
- writeClassCatchingErrors(clazz, consumer, rewriter, markerString, sourceFileEnvironment);
+ new CfApplicationClassWriter(appView, clazz)
+ .writeClassCatchingErrors(consumer, rewriter, markerString, sourceFileEnvironment);
}
if (!globalSyntheticClasses.isEmpty()) {
InternalGlobalSyntheticsCfConsumer globalsConsumer =
new InternalGlobalSyntheticsCfConsumer(options.getGlobalSyntheticsConsumer(), appView);
for (DexProgramClass clazz : globalSyntheticClasses) {
- writeClassCatchingErrors(
- clazz, globalsConsumer, rewriter, markerString, sourceFileEnvironment);
+ new CfApplicationClassWriter(appView, clazz)
+ .writeClassCatchingErrors(
+ globalsConsumer, rewriter, markerString, sourceFileEnvironment);
}
globalsConsumer.finished(appView);
}
ApplicationWriter.supplyAdditionalConsumers(appView, executorService, Collections.emptyList());
}
-
- private void writeClassCatchingErrors(
- DexProgramClass clazz,
- ClassFileConsumer consumer,
- LensCodeRewriterUtils rewriter,
- Optional<String> markerString,
- SourceFileEnvironment sourceFileEnvironment) {
- assert SyntheticNaming.verifyNotInternalSynthetic(clazz.getType());
- try {
- writeClass(clazz, consumer, rewriter, markerString, sourceFileEnvironment);
- } 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()));
- }
- }
-
- private void writeClass(
- DexProgramClass clazz,
- ClassFileConsumer consumer,
- LensCodeRewriterUtils rewriter,
- Optional<String> markerString,
- SourceFileEnvironment sourceFileEnvironment) {
- ClassWriter writer = new ClassWriter(0);
- if (markerString.isPresent()) {
- int markerStringPoolIndex = writer.newConst(markerString.get());
- assert markerStringPoolIndex == MARKER_STRING_CONSTANT_POOL_INDEX;
- }
- String sourceFile;
- if (options.sourceFileProvider == null) {
- sourceFile = clazz.sourceFile != null ? clazz.sourceFile.toString() : null;
- } else {
- sourceFile = options.sourceFileProvider.get(sourceFileEnvironment);
- }
- String sourceDebug = getSourceDebugExtension(clazz.annotations());
- writer.visitSource(sourceFile, 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();
- }
- }
- boolean allowInvalidCfAccessFlags = false;
- if (clazz
- .getType()
- .getDescriptor()
- .endsWith(appView.dexItemFactory().createString("/package-info;"))) {
- allowInvalidCfAccessFlags = true;
- }
- int access =
- allowInvalidCfAccessFlags || options.testing.allowInvalidCfAccessFlags
- ? clazz.accessFlags.materialize()
- : clazz.accessFlags.getAsCfAccessFlags();
- if (clazz.isDeprecated()) {
- access = AsmUtils.withDeprecated(access);
- }
- String desc = getNamingLens().lookupDescriptor(clazz.type).toString();
- String name = getNamingLens().lookupInternalName(clazz.type);
- String signature = clazz.getClassSignature().toRenamedString(getNamingLens(), isTypeMissing);
- String superName =
- clazz.hasSuperType() ? getNamingLens().lookupInternalName(clazz.superType) : null;
- String[] interfaces = new String[clazz.interfaces.values.length];
- for (int i = 0; i < clazz.interfaces.values.length; i++) {
- interfaces[i] = getNamingLens().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, writer::visitTypeAnnotation, clazz.annotations().annotations);
- ImmutableMap<DexString, DexValue> defaults = getAnnotationDefaults(clazz.annotations());
-
- if (clazz.getEnclosingMethodAttribute() != null) {
- clazz.getEnclosingMethodAttribute().write(writer, getNamingLens());
- }
-
- if (clazz.getNestHostClassAttribute() != null) {
- clazz.getNestHostClassAttribute().write(writer, getNamingLens());
- }
-
- for (NestMemberClassAttribute entry : clazz.getNestMembersClassAttributes()) {
- entry.write(writer, getNamingLens());
- assert clazz.getNestHostClassAttribute() == null
- : "A nest host cannot also be a nest member.";
- }
-
- for (PermittedSubclassAttribute entry : clazz.getPermittedSubclassAttributes()) {
- entry.write(writer, getNamingLens());
- }
-
- if (clazz.isRecord()) {
- // TODO(b/274888318): Strip record components if not kept.
- for (RecordComponentInfo info : clazz.getRecordComponents()) {
- info.write(writer, getNamingLens(), isTypeMissing, this::writeAnnotation);
- }
- }
-
- for (InnerClassAttribute entry : clazz.getInnerClasses()) {
- entry.write(writer, getNamingLens(), 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 getNamingLens().lookupDescriptor(a).compareTo(getNamingLens().lookupDescriptor(b));
- }
-
- private DexString returnTypeThroughLens(DexMethod method) {
- return getNamingLens().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.getLibraryDesugaringOptions().isDesugaredLibraryCompilation()
- || options.isDesugaring()
- : "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.isDesugaring()
- ? 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();
- }
-
- @SuppressWarnings("BadImport")
- 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] = getNamingLens().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 = getNamingLens().lookupName(field.getReference()).toString();
- String desc = getNamingLens().lookupDescriptor(field.getReference().type).toString();
- String signature = field.getGenericSignature().toRenamedString(getNamingLens(), isTypeMissing);
- Object value = getStaticValue(field);
- FieldVisitor visitor = writer.visitField(access, name, desc, signature, value);
- writeAnnotations(
- visitor::visitAnnotation, visitor::visitTypeAnnotation, field.annotations().annotations);
- visitor.visitEnd();
- }
-
- private void writeMethod(
- ProgramMethod method,
- CfVersion classFileVersion,
- LensCodeRewriterUtils rewriter,
- ClassWriter writer,
- ImmutableMap<DexString, DexValue> defaults) {
- // For "pass through" classes which has already been library desugared use the identity lens.
- NamingLens namingLens =
- appView.isAlreadyLibraryDesugared(method.getHolder())
- ? NamingLens.getIdentityLens()
- : getNamingLens();
- 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,
- visitor::visitTypeAnnotation,
- definition.annotations().annotations);
- writeParameterAnnotations(visitor, definition.parameterAnnotationsList);
- if (!definition.shouldNotHaveCode()) {
- writeCode(method, classFileVersion, namingLens, rewriter, visitor);
- }
- visitor.visitEnd();
- }
-
- @SuppressWarnings("ReferenceEquality")
- 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();
- String nameString = name != null ? name.value.toString() : null;
- visitor.visitParameter(nameString, 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),
- (typeRef, typePath, desc, visible) -> {
- throw new Unreachable("Type annotations are not placed on parameters");
- },
- parameterAnnotations.get(i).annotations);
- }
- }
-
- private interface AnnotationConsumer {
- AnnotationVisitor visit(String desc, boolean visible);
- }
-
- private interface TypeAnnotationConsumer {
- AnnotationVisitor visit(int typeRef, TypePath typePath, String desc, boolean visible);
- }
-
- private void writeAnnotations(
- AnnotationConsumer visitor,
- TypeAnnotationConsumer typeAnnotationVisitor,
- 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;
- }
- String desc = getNamingLens().lookupDescriptor(dexAnnotation.annotation.type).toString();
- boolean visible = dexAnnotation.visibility == DexAnnotation.VISIBILITY_RUNTIME;
- DexTypeAnnotation dexTypeAnnotation = dexAnnotation.asTypeAnnotation();
- AnnotationVisitor v =
- dexTypeAnnotation == null
- ? visitor.visit(desc, visible)
- : typeAnnotationVisitor.visit(
- dexTypeAnnotation.getTypeRef(), dexTypeAnnotation.getTypePath(), desc, visible);
- 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);
- }
- }
-
- @SuppressWarnings("ReferenceEquality")
- private void writeAnnotationElement(AnnotationVisitor visitor, String name, DexValue value) {
- switch (value.getValueKind()) {
- case ANNOTATION:
- {
- DexValueAnnotation valueAnnotation = value.asDexValueAnnotation();
- AnnotationVisitor innerVisitor =
- visitor.visitAnnotation(
- name, getNamingLens().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();
- // This must not be renamed, as the Java runtime will use Enum.valueOf to find the enum's
- // referenced in annotations. See b/236691999 for details.
- assert getNamingLens().lookupName(enumField) == enumField.name
- || System.getProperty("com.android.tools.r8.tracereferences.obfuscateAllEnums")
- != null
- : "Enum field "
- + enumField.name
- + " renamed to "
- + getNamingLens().lookupName(enumField);
- visitor.visitEnum(
- name,
- getNamingLens().lookupDescriptor(enumField.getType()).toString(),
- enumField.name.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(
- getNamingLens().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) {
- Code code = method.getDefinition().getCode();
- assert code.isCfWritableCode();
- assert code.estimatedDexCodeSizeUpperBoundInBytes() > 0;
- if (!code.isCfWritableCode()) {
- // This should never happen (see assertion above), but desugaring bugs may lead the
- // CfApplicationWriter to try to write invalid code and we need the better error message.
- throw new Unreachable(
- "The CfApplicationWriter cannot write non cf writable code "
- + code.getClass().getCanonicalName()
- + " for method "
- + method.getReference().toSourceString());
- }
- code.asCfWritableCode()
- .writeCf(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();
- }
-
- @SuppressWarnings("DefaultCharset")
- private static void verifyCf(byte[] result) {
- ClassReader reader = new ClassReader(result);
- PrintWriter pw = new PrintWriter(System.out);
- CheckClassAdapter.verify(reader, false, pw);
- }
}