blob: 0c78a723071cc4c21b7ef70449f0f305fecd9dbf [file] [log] [blame]
// Copyright (c) 2016, 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.graph;
import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
import static org.objectweb.asm.ClassReader.SKIP_CODE;
import static org.objectweb.asm.ClassReader.SKIP_DEBUG;
import static org.objectweb.asm.ClassReader.SKIP_FRAMES;
import static org.objectweb.asm.Opcodes.ACC_DEPRECATED;
import com.android.tools.r8.ProgramResource;
import com.android.tools.r8.ProgramResource.Kind;
import com.android.tools.r8.ResourceException;
import com.android.tools.r8.cf.CfVersion;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier;
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.DexValueFloat;
import com.android.tools.r8.graph.DexValue.DexValueInt;
import com.android.tools.r8.graph.DexValue.DexValueLong;
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.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.origin.Origin;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.synthesis.SyntheticMarker;
import com.android.tools.r8.utils.AsmUtils;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.FieldSignatureEquivalence;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.zip.CRC32;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.RecordComponentVisitor;
import org.objectweb.asm.Type;
import org.objectweb.asm.TypePath;
/** Java/Jar class reader for constructing dex/graph structure. */
public class JarClassFileReader<T extends DexClass> {
private static final byte[] CLASSFILE_HEADER = ByteBuffer.allocate(4).putInt(0xCAFEBABE).array();
// Hidden ASM "synthetic attribute" bit we need to clear.
private static final int ACC_SYNTHETIC_ATTRIBUTE = 0x40000;
private final JarApplicationReader application;
private final Consumer<T> classConsumer;
private final ClassKind<T> classKind;
public JarClassFileReader(
JarApplicationReader application, Consumer<T> classConsumer, ClassKind<T> classKind) {
this.application = application;
this.classConsumer = classConsumer;
this.classKind = classKind;
}
public void read(ProgramResource resource) throws ResourceException {
read(resource.getOrigin(), resource.getBytes());
}
public void read(Origin origin, byte[] bytes) {
ExceptionUtils.withOriginAttachmentHandler(origin, () -> internalRead(origin, bytes));
}
public void internalRead(Origin origin, byte[] bytes) {
if (bytes.length < CLASSFILE_HEADER.length) {
throw new CompilationError("Invalid empty classfile", origin);
}
for (int i = 0; i < CLASSFILE_HEADER.length; i++) {
if (bytes[i] != CLASSFILE_HEADER[i]) {
throw new CompilationError("Invalid classfile header", origin);
}
}
ClassReader reader = new ClassReader(bytes);
int parsingOptions = SKIP_FRAMES | SKIP_CODE;
// If the source-file and source-debug-extension attributes are not kept we can skip all debug
// related attributes when parsing the class structure.
if (application.options.getProguardConfiguration() != null) {
ProguardKeepAttributes keep =
application.options.getProguardConfiguration().getKeepAttributes();
if (!keep.sourceFile && !keep.sourceDebugExtension && !keep.methodParameters) {
parsingOptions |= SKIP_DEBUG;
}
}
reader.accept(
new CreateDexClassVisitor<>(origin, classKind, reader.b, application, classConsumer),
new Attribute[] {SyntheticMarker.getMarkerAttributePrototype()},
parsingOptions);
// Read marker.
if (reader.getItemCount() > CfApplicationWriter.MARKER_STRING_CONSTANT_POOL_INDEX
&& reader.getItem(CfApplicationWriter.MARKER_STRING_CONSTANT_POOL_INDEX) > 0) {
try {
Object maybeMarker =
reader.readConst(
CfApplicationWriter.MARKER_STRING_CONSTANT_POOL_INDEX,
new char[reader.getMaxStringLength()]);
if (maybeMarker instanceof String) {
application.getFactory().createString((String) maybeMarker);
}
} catch (IllegalArgumentException e) {
// Ignore if the type of the constant is not something readConst() allows.
}
}
}
private static int cleanAccessFlags(int access) {
// Clear the "synthetic attribute" and "deprecated" attribute-flags if present.
return access & ~ACC_SYNTHETIC_ATTRIBUTE & ~ACC_DEPRECATED;
}
public static FieldAccessFlags createFieldAccessFlags(int access) {
return FieldAccessFlags.fromCfAccessFlags(cleanAccessFlags(access));
}
public static MethodAccessFlags createMethodAccessFlags(String name, int access) {
boolean isConstructor =
name.equals(Constants.INSTANCE_INITIALIZER_NAME)
|| name.equals(Constants.CLASS_INITIALIZER_NAME);
return MethodAccessFlags.fromCfAccessFlags(cleanAccessFlags(access), isConstructor);
}
private static AnnotationVisitor createAnnotationVisitor(String desc, boolean visible,
List<DexAnnotation> annotations,
JarApplicationReader application) {
assert annotations != null;
if (visible || retainCompileTimeAnnotation(desc, application)) {
int visiblity = visible ? DexAnnotation.VISIBILITY_RUNTIME : DexAnnotation.VISIBILITY_BUILD;
return new CreateAnnotationVisitor(
application,
(names, values) ->
annotations.add(
new DexAnnotation(
visiblity, createEncodedAnnotation(desc, names, values, application))));
}
return null;
}
private static boolean retainCompileTimeAnnotation(
String desc, JarApplicationReader application) {
return application.options.readCompileTimeAnnotations
|| DexAnnotation.retainCompileTimeAnnotation(
application.getTypeFromDescriptor(desc), application.options);
}
private static DexEncodedAnnotation createEncodedAnnotation(String desc,
List<DexString> names, List<DexValue> values, JarApplicationReader application) {
assert (names == null && values.isEmpty())
|| (names != null && !names.isEmpty() && names.size() == values.size());
DexAnnotationElement[] elements = new DexAnnotationElement[values.size()];
for (int i = 0; i < values.size(); i++) {
elements[i] = new DexAnnotationElement(names.get(i), values.get(i));
}
return new DexEncodedAnnotation(application.getTypeFromDescriptor(desc), elements);
}
private static class CreateDexClassVisitor<T extends DexClass> extends ClassVisitor {
private final Origin origin;
private final ClassKind<T> classKind;
private final JarApplicationReader application;
private final Consumer<T> classConsumer;
private final ReparseContext context = new ReparseContext();
// DexClass data.
private CfVersion version;
private boolean deprecated;
private DexType type;
private ClassAccessFlags accessFlags;
private DexType superType;
private DexTypeList interfaces;
private DexString sourceFile;
private NestHostClassAttribute nestHost = null;
private final List<NestMemberClassAttribute> nestMembers = new ArrayList<>();
private final Set<DexField> recordComponents = Sets.newIdentityHashSet();
private EnclosingMethodAttribute enclosingMember = null;
private final List<InnerClassAttribute> innerClasses = new ArrayList<>();
private ClassSignature classSignature = ClassSignature.noSignature();
private List<DexAnnotation> annotations = null;
private List<DexAnnotationElement> defaultAnnotations = null;
private final List<DexEncodedField> staticFields = new ArrayList<>();
private final List<DexEncodedField> instanceFields = new ArrayList<>();
private final Set<Wrapper<DexField>> fieldSignatures = new HashSet<>();
private final List<DexEncodedMethod> directMethods = new ArrayList<>();
private final List<DexEncodedMethod> virtualMethods = new ArrayList<>();
private final Set<Wrapper<DexMethod>> methodSignatures = new HashSet<>();
private boolean hasReachabilitySensitiveMethod = false;
private SyntheticMarker syntheticMarker = null;
public CreateDexClassVisitor(
Origin origin,
ClassKind<T> classKind,
byte[] classCache,
JarApplicationReader application,
Consumer<T> classConsumer) {
super(ASM_VERSION);
this.origin = origin;
this.classKind = classKind;
this.classConsumer = classConsumer;
this.context.classCache = classCache;
this.application = application;
}
@Override
public void visitAttribute(Attribute attribute) {
SyntheticMarker marker = SyntheticMarker.readMarkerAttribute(attribute);
if (marker != null) {
assert syntheticMarker == null;
syntheticMarker = marker;
}
}
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
if (outerName != null && innerName != null) {
String separator = DescriptorUtils.computeInnerClassSeparator(outerName, name, innerName);
if (separator == null && version.isLessThan(CfVersion.V9)) {
application.options.reporter.info(
new StringDiagnostic(
StringUtils.lines(
"Malformed inner-class attribute:",
"\touterTypeInternal: " + outerName,
"\tinnerTypeInternal: " + name,
"\tinnerName: " + innerName),
origin));
}
}
innerClasses.add(
new InnerClassAttribute(
access,
application.getTypeFromName(name),
outerName == null ? null : application.getTypeFromName(outerName),
innerName == null ? null : application.getString(innerName)));
}
@Override
public void visitOuterClass(String owner, String name, String desc) {
// This is called for anonymous and local inner classes defined in classes or in methods.
assert enclosingMember == null;
DexType ownerType = application.getTypeFromName(owner);
enclosingMember =
name == null || name.equals("<clinit>")
? new EnclosingMethodAttribute(ownerType)
: new EnclosingMethodAttribute(application.getMethod(ownerType, name, desc));
}
@Override
public void visitNestHost(String nestHost) {
assert this.nestHost == null && nestMembers.isEmpty();
DexType nestHostType = application.getTypeFromName(nestHost);
this.nestHost = new NestHostClassAttribute(nestHostType);
}
@Override
public void visitNestMember(String nestMember) {
assert nestHost == null;
DexType nestMemberType = application.getTypeFromName(nestMember);
nestMembers.add(new NestMemberClassAttribute(nestMemberType));
}
private String illegalClassFilePrefix(ClassAccessFlags accessFlags, String name) {
return "Illegal class file: "
+ (accessFlags.isInterface() ? "Interface" : "Class")
+ " "
+ name;
}
private String illegalClassFilePostfix(CfVersion version) {
return "Class file version " + version;
}
private String illegalClassFileMessage(
ClassAccessFlags accessFlags, String name, CfVersion version, String message) {
return illegalClassFilePrefix(accessFlags, name)
+ " " + message
+ ". " + illegalClassFilePostfix(version) + ".";
}
@Override
public RecordComponentVisitor visitRecordComponent(
String name, String descriptor, String signature) {
assert name != null;
assert descriptor != null;
// Javac generated record components are only the instance fields, so we just reuse the field
// to avoid duplicating the field and field signature rewriting logic.
DexField field =
application
.getFactory()
.createField(
type, application.getTypeFromDescriptor(descriptor), application.getString(name));
recordComponents.add(field);
return super.visitRecordComponent(name, descriptor, signature);
}
@Override
public void visitPermittedSubclass(String permittedSubclass) {
throw new CompilationError("Sealed classes are not supported", origin);
}
@Override
public void visit(
int rawVersion,
int access,
String name,
String signature,
String superName,
String[] interfaces) {
version = CfVersion.fromRaw(rawVersion);
if (InternalOptions.SUPPORTED_CF_VERSION.isLessThan(version)) {
throw new CompilationError("Unsupported class file version: " + version, origin);
}
if (version.isGreaterThanOrEqualTo(InternalOptions.EXPERIMENTAL_CF_VERSION)) {
application.options.warningExperimentalClassFileVersion(origin);
}
this.deprecated = AsmUtils.isDeprecated(access);
accessFlags = ClassAccessFlags.fromCfAccessFlags(cleanAccessFlags(access));
type = application.getTypeFromName(name);
// Check if constraints from
// https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.1 are met.
if (!accessFlags.areValid(version, name.endsWith("/package-info"))) {
throw new CompilationError(
illegalClassFileMessage(
accessFlags,
name,
version,
"has invalid access flags. Found: " + accessFlags.toString()),
origin);
}
if (superName == null && !name.equals(Constants.JAVA_LANG_OBJECT_NAME)) {
throw new CompilationError(
illegalClassFileMessage(accessFlags, name, version, "is missing a super type"), origin);
}
if (accessFlags.isInterface()
&& !Objects.equals(superName, Constants.JAVA_LANG_OBJECT_NAME)) {
throw new CompilationError(
illegalClassFileMessage(
accessFlags,
name,
version,
"must extend class java.lang.Object. Found: " + superName),
origin);
}
checkName(name);
assert superName != null || name.equals(Constants.JAVA_LANG_OBJECT_NAME);
superType = superName == null ? null : application.getTypeFromName(superName);
this.interfaces = application.getTypeListFromNames(interfaces);
if (application.options.parseSignatureAttribute()) {
classSignature =
GenericSignature.parseClassSignature(
name, signature, origin, application.getFactory(), application.options.reporter);
}
}
@Override
public void visitSource(String source, String debug) {
if (source != null) {
sourceFile = application.getString(source);
}
if (debug != null) {
getAnnotations().add(
DexAnnotation.createSourceDebugExtensionAnnotation(
new DexValueString(application.getString(debug)), application.getFactory()));
}
}
@Override
public FieldVisitor visitField(
int access, String name, String desc, String signature, Object value) {
if (classKind == ClassKind.LIBRARY) {
FieldAccessFlags flags = createFieldAccessFlags(access);
if (flags.isPrivate()) {
return null;
}
}
checkName(name);
return new CreateFieldVisitor(
this, access, name, desc, signature, classKind == ClassKind.LIBRARY ? null : value);
}
@Override
public MethodVisitor visitMethod(
int access, String name, String desc, String signature, String[] exceptions) {
if (classKind == ClassKind.LIBRARY) {
MethodAccessFlags flags = createMethodAccessFlags(name, access);
if ((flags.isStatic() && flags.isConstructor()) || flags.isPrivate()) {
return null;
}
}
checkName(name);
return new CreateMethodVisitor(access, name, desc, signature, exceptions, this);
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
return createAnnotationVisitor(desc, visible, getAnnotations(), application);
}
@Override
public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc,
boolean visible) {
// Java 8 type annotations are not supported by Dex, thus ignore them.
return null;
}
@Override
public void visitEnd() {
if (defaultAnnotations != null) {
addAnnotation(DexAnnotation.createAnnotationDefaultAnnotation(
type, defaultAnnotations, application.getFactory()));
}
checkReachabilitySensitivity();
checkRecord();
T clazz =
classKind.create(
type,
Kind.CF,
origin,
accessFlags,
superType,
interfaces,
sourceFile,
nestHost,
nestMembers,
enclosingMember,
innerClasses,
classSignature,
createAnnotationSet(annotations, application.options),
staticFields.toArray(DexEncodedField.EMPTY_ARRAY),
instanceFields.toArray(DexEncodedField.EMPTY_ARRAY),
directMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
virtualMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
application.getFactory().getSkipNameValidationForTesting(),
getChecksumSupplier(classKind),
syntheticMarker);
InnerClassAttribute innerClassAttribute = clazz.getInnerClassAttributeForThisClass();
// A member class should not be a local or anonymous class.
if (innerClassAttribute != null && innerClassAttribute.getOuter() != null) {
if (innerClassAttribute.isAnonymous()) {
assert innerClassAttribute.getInnerName() == null;
// If the enclosing member is not null, the intention would be an anonymous class, and
// thus the outer-class reference should have been null. If the enclosing member is null,
// it is likely due to the missing enclosing member. In either case, we can recover
// InnerClasses attribute by erasing the outer-class reference.
InnerClassAttribute recoveredAttribute = new InnerClassAttribute(
innerClassAttribute.getAccess(), innerClassAttribute.getInner(), null, null);
clazz.replaceInnerClassAttributeForThisClass(recoveredAttribute);
} else if (enclosingMember != null) {
assert innerClassAttribute.isNamed();
// It is unclear whether the intention was a member class or a local class. Fail hard.
throw new CompilationError(
StringUtils.lines(
"A member class cannot also be a (non-member) local class at the same time.",
"This is likely due to invalid EnclosingMethod and InnerClasses attributes:",
enclosingMember.toString(),
innerClassAttribute.toString()),
origin);
}
}
if (enclosingMember == null
&& (clazz.isLocalClass() || clazz.isAnonymousClass())
&& CfVersion.V1_6.isLessThan(version)) {
application.options.warningMissingEnclosingMember(clazz.type, clazz.origin, version);
}
if (!clazz.isLibraryClass()) {
context.owner = clazz;
}
if (clazz.isProgramClass()) {
DexProgramClass programClass = clazz.asProgramClass();
programClass.setInitialClassFileVersion(version);
if (deprecated) {
programClass.setDeprecated();
}
}
classConsumer.accept(clazz);
}
private void checkRecord() {
if (!application.options.shouldDesugarRecords()) {
return;
}
if (!accessFlags.isRecord()) {
return;
}
application.setHasReadRecordReferenceFromProgramClass();
// TODO(b/169645628): Change this logic if we start stripping the record components.
// Another approach would be to mark a bit in fields that are record components instead.
String message = "Records are expected to have one record component per instance field.";
if (recordComponents.size() != instanceFields.size()) {
throw new CompilationError(message, origin);
}
for (DexEncodedField instanceField : instanceFields) {
if (!recordComponents.contains(instanceField.getReference())) {
throw new CompilationError(
message + " Unmatched field " + instanceField.getReference() + ".", origin);
}
}
}
private ChecksumSupplier getChecksumSupplier(ClassKind<T> classKind) {
if (application.options.encodeChecksums && classKind == ClassKind.PROGRAM) {
CRC32 crc = new CRC32();
crc.update(this.context.classCache, 0, this.context.classCache.length);
final long value = crc.getValue();
return clazz -> value;
}
return DexProgramClass::invalidChecksumRequest;
}
private void checkName(String name) {
if (!application.getFactory().getSkipNameValidationForTesting()
&& !DexString.isValidSimpleName(application.options.minApiLevel, name)) {
throw new CompilationError("Space characters in SimpleName '"
+ name + "' are not allowed prior to DEX version 040");
}
}
// If anything is marked reachability sensitive, all methods need to be parsed including
// locals information. This propagates the reachability sensitivity bit so that if any field
// or method is annotated, all methods get parsed with locals information.
private void checkReachabilitySensitivity() {
if (hasReachabilitySensitiveMethod || hasReachabilitySensitiveField()) {
for (DexEncodedMethod method : Iterables.concat(directMethods, virtualMethods)) {
Code code = method.getCode();
if (code != null && code.isCfCode()) {
code.asLazyCfCode().markReachabilitySensitive();
}
}
}
}
private boolean hasReachabilitySensitiveField() {
DexType reachabilitySensitive = application.getFactory().annotationReachabilitySensitive;
for (DexEncodedField field : Iterables.concat(instanceFields, staticFields)) {
for (DexAnnotation annotation : field.annotations().annotations) {
if (annotation.annotation.type == reachabilitySensitive) {
return true;
}
}
}
return false;
}
private void addDefaultAnnotation(String name, DexValue value) {
if (defaultAnnotations == null) {
defaultAnnotations = new ArrayList<>();
}
defaultAnnotations.add(new DexAnnotationElement(application.getString(name), value));
}
private void addAnnotation(DexAnnotation annotation) {
getAnnotations().add(annotation);
}
private List<DexAnnotation> getAnnotations() {
if (annotations == null) {
annotations = new ArrayList<>();
}
return annotations;
}
public boolean isInANest() {
return !nestMembers.isEmpty() || nestHost != null;
}
}
private static DexAnnotationSet createAnnotationSet(
List<DexAnnotation> annotations, InternalOptions options) {
if (annotations == null || annotations.isEmpty()) {
return DexAnnotationSet.empty();
}
if (options.isGeneratingDex()) {
DexType dupType = DexAnnotationSet.findDuplicateEntryType(annotations);
if (dupType != null) {
throw new CompilationError(
"Multiple annotations of type `" + dupType.toSourceString() + "`");
}
}
return DexAnnotationSet.create(annotations.toArray(DexAnnotation.EMPTY_ARRAY));
}
private static class CreateFieldVisitor extends FieldVisitor {
private final CreateDexClassVisitor<?> parent;
private final int access;
private final String name;
private final String desc;
private final Object value;
private final FieldTypeSignature fieldSignature;
private List<DexAnnotation> annotations = null;
public CreateFieldVisitor(
CreateDexClassVisitor<?> parent,
int access,
String name,
String desc,
String signature,
Object value) {
super(ASM_VERSION);
this.parent = parent;
this.access = access;
this.name = name;
this.desc = desc;
this.value = value;
this.fieldSignature =
parent.application.options.parseSignatureAttribute()
? GenericSignature.parseFieldTypeSignature(
name,
signature,
parent.origin,
parent.application.getFactory(),
parent.application.options.reporter)
: FieldTypeSignature.noSignature();
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
return createAnnotationVisitor(desc, visible, getAnnotations(), parent.application);
}
@Override
public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc,
boolean visible) {
// Java 8 type annotations are not supported by Dex, thus ignore them.
return null;
}
@Override
public void visitEnd() {
FieldAccessFlags flags = createFieldAccessFlags(access);
DexField dexField = parent.application.getField(parent.type, name, desc);
parent.application.checkFieldForRecord(dexField);
Wrapper<DexField> signature = FieldSignatureEquivalence.get().wrap(dexField);
if (parent.fieldSignatures.add(signature)) {
DexAnnotationSet annotationSet =
createAnnotationSet(annotations, parent.application.options);
DexValue staticValue = flags.isStatic() ? getStaticValue(value, dexField.type) : null;
DexEncodedField field =
DexEncodedField.builder()
.setField(dexField)
.setAccessFlags(flags)
.setGenericSignature(fieldSignature)
.setAnnotations(annotationSet)
.setStaticValue(staticValue)
.setDeprecated(AsmUtils.isDeprecated(access))
.disableAndroidApiLevelCheck()
.build();
if (flags.isStatic()) {
parent.staticFields.add(field);
} else {
parent.instanceFields.add(field);
}
} else {
parent.application.options.reporter.warning(
new StringDiagnostic(
String.format("Field `%s` has multiple definitions", dexField.toSourceString())));
}
}
private DexValue getStaticValue(Object value, DexType type) {
if (value == null) {
return null;
}
DexItemFactory factory = parent.application.getFactory();
if (type == factory.booleanType) {
int i = (Integer) value;
assert 0 <= i && i <= 1;
return DexValueBoolean.create(i == 1);
}
if (type == factory.byteType) {
return DexValueByte.create(((Integer) value).byteValue());
}
if (type == factory.shortType) {
return DexValueShort.create(((Integer) value).shortValue());
}
if (type == factory.charType) {
return DexValueChar.create((char) ((Integer) value).intValue());
}
if (type == factory.intType) {
return DexValueInt.create((Integer) value);
}
if (type == factory.floatType) {
return DexValueFloat.create((Float) value);
}
if (type == factory.longType) {
return DexValueLong.create((Long) value);
}
if (type == factory.doubleType) {
return DexValueDouble.create((Double) value);
}
if (type == factory.stringType) {
return new DexValueString(factory.createString((String) value));
}
throw new Unreachable("Unexpected static-value type " + type);
}
private List<DexAnnotation> getAnnotations() {
if (annotations == null) {
annotations = new ArrayList<>();
}
return annotations;
}
}
private static class CreateMethodVisitor extends MethodVisitor {
private final String name;
final CreateDexClassVisitor<?> parent;
private final int parameterCount;
private List<DexAnnotation> annotations = null;
private DexValue defaultAnnotation = null;
private int annotableParameterCount = -1;
private List<List<DexAnnotation>> parameterAnnotationsLists = null;
private List<DexValue> parameterNames = null;
private List<DexValue> parameterFlags = null;
private final MethodTypeSignature genericSignature;
final DexMethod method;
final MethodAccessFlags flags;
final boolean deprecated;
Code code = null;
public CreateMethodVisitor(
int access,
String name,
String desc,
String signature,
String[] exceptions,
CreateDexClassVisitor<?> parent) {
super(ASM_VERSION);
this.name = name;
this.parent = parent;
this.method = parent.application.getMethod(parent.type, name, desc);
this.flags = createMethodAccessFlags(name, access);
this.deprecated = AsmUtils.isDeprecated(access);
parameterCount = DescriptorUtils.getArgumentCount(desc);
if (exceptions != null && exceptions.length > 0) {
DexValue[] values = new DexValue[exceptions.length];
for (int i = 0; i < exceptions.length; i++) {
values[i] = new DexValueType(parent.application.getTypeFromName(exceptions[i]));
}
addAnnotation(DexAnnotation.createThrowsAnnotation(
values, parent.application.getFactory()));
}
genericSignature =
parent.application.options.parseSignatureAttribute()
? GenericSignature.parseMethodSignature(
name,
signature,
parent.origin,
parent.application.getFactory(),
parent.application.options.reporter)
: MethodTypeSignature.noSignature();
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
return createAnnotationVisitor(desc, visible, getAnnotations(), parent.application);
}
@Override
public AnnotationVisitor visitAnnotationDefault() {
return new CreateAnnotationVisitor(parent.application, (names, elements) -> {
assert elements.size() == 1;
defaultAnnotation = elements.get(0);
});
}
@Override
public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc,
boolean visible) {
// Java 8 type annotations are not supported by Dex, thus ignore them.
return null;
}
@Override
public void visitAnnotableParameterCount(int parameterCount, boolean visible) {
if (annotableParameterCount != -1) {
// TODO(113565942): We assume that the runtime visible and runtime invisible parameter
// count is always the same. It doesn't have to be according to the classfile format, so
// we really should split it into two sets.
assert annotableParameterCount == parameterCount;
}
annotableParameterCount = parameterCount;
}
@Override
public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
if (parameterAnnotationsLists == null) {
if (annotableParameterCount == -1) {
annotableParameterCount = parameterCount;
}
parameterAnnotationsLists = new ArrayList<>(annotableParameterCount);
for (int i = 0; i < annotableParameterCount; i++) {
parameterAnnotationsLists.add(new ArrayList<>());
}
}
assert mv == null;
return createAnnotationVisitor(desc, visible,
parameterAnnotationsLists.get(parameter), parent.application);
}
@Override
public AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String desc,
boolean visible) {
// Java 8 type annotations are not supported by Dex, thus ignore them.
return null;
}
@Override
public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath,
Label[] start, Label[] end, int[] index, String desc, boolean visible) {
// Java 8 type annotations are not supported by Dex, thus ignore them.
return null;
}
@Override
public AnnotationVisitor visitTryCatchAnnotation(int typeRef, TypePath typePath, String desc,
boolean visible) {
// Java 8 type annotations are not supported by Dex, thus ignore them.
return null;
}
@Override
public void visitParameter(String name, int access) {
if (parameterNames == null) {
assert parameterFlags == null;
parameterNames = new ArrayList<>(parameterCount);
parameterFlags = new ArrayList<>(parameterCount);
}
parameterNames.add(new DexValueString(parent.application.getFactory().createString(name)));
parameterFlags.add(DexValueInt.create(access));
super.visitParameter(name, access);
}
@Override
public void visitCode() {
throw new Unreachable("visitCode() should not be called when SKIP_CODE is set");
}
private boolean classRequiresCode() {
return parent.classKind == ClassKind.PROGRAM
|| (!parent.application.options.canUseNestBasedAccess()
&& parent.classKind == ClassKind.CLASSPATH
&& parent.isInANest());
}
@Override
public void visitEnd() {
InternalOptions options = parent.application.options;
parent.application.checkMethodForRecord(method);
if (!flags.isAbstract() && !flags.isNative() && classRequiresCode()) {
code = new LazyCfCode(method, parent.origin, parent.context, parent.application);
}
ParameterAnnotationsList parameterAnnotationsList;
if (parameterAnnotationsLists == null) {
parameterAnnotationsList = ParameterAnnotationsList.empty();
} else {
DexAnnotationSet[] sets = new DexAnnotationSet[parameterAnnotationsLists.size()];
for (int i = 0; i < parameterAnnotationsLists.size(); i++) {
sets[i] = createAnnotationSet(parameterAnnotationsLists.get(i), options);
}
parameterAnnotationsList = ParameterAnnotationsList.create(sets);
}
if (parameterNames != null) {
assert parameterFlags != null;
if (parameterNames.size() != parameterCount) {
options.warningInvalidParameterAnnotations(
method, parent.origin, parameterCount, parameterNames.size());
}
getAnnotations().add(DexAnnotation.createMethodParametersAnnotation(
parameterNames.toArray(DexValue.EMPTY_ARRAY),
parameterFlags.toArray(DexValue.EMPTY_ARRAY),
parent.application.getFactory()));
}
DexEncodedMethod dexMethod =
DexEncodedMethod.builder()
.setMethod(method)
.setAccessFlags(flags)
.setGenericSignature(genericSignature)
.setAnnotations(createAnnotationSet(annotations, options))
.setParameterAnnotations(parameterAnnotationsList)
.setCode(code)
.setClassFileVersion(parent.version)
.setDeprecated(deprecated)
.disableParameterAnnotationListCheck()
.disableAndroidApiLevelCheck()
.build();
Wrapper<DexMethod> signature = MethodSignatureEquivalence.get().wrap(method);
if (parent.methodSignatures.add(signature)) {
parent.hasReachabilitySensitiveMethod |= isReachabilitySensitive();
if (flags.isStatic() || flags.isConstructor() || flags.isPrivate()) {
parent.directMethods.add(dexMethod);
} else {
parent.virtualMethods.add(dexMethod);
}
} else {
options.reporter.warning(
new StringDiagnostic(
String.format(
"Ignoring an implementation of the method `%s` because it has multiple "
+ "definitions",
method.toSourceString())));
}
if (defaultAnnotation != null) {
parent.addDefaultAnnotation(name, defaultAnnotation);
}
}
private boolean isReachabilitySensitive() {
DexType reachabilitySensitive =
parent.application.getFactory().annotationReachabilitySensitive;
for (DexAnnotation annotation : getAnnotations()) {
if (annotation.annotation.type == reachabilitySensitive) {
return true;
}
}
return false;
}
private List<DexAnnotation> getAnnotations() {
if (annotations == null) {
annotations = new ArrayList<>();
}
return annotations;
}
private void addAnnotation(DexAnnotation annotation) {
getAnnotations().add(annotation);
}
}
private static class CreateAnnotationVisitor extends AnnotationVisitor {
private final JarApplicationReader application;
private final BiConsumer<List<DexString>, List<DexValue>> onVisitEnd;
private List<DexString> names = null;
private final List<DexValue> values = new ArrayList<>();
public CreateAnnotationVisitor(
JarApplicationReader application, BiConsumer<List<DexString>, List<DexValue>> onVisitEnd) {
super(ASM_VERSION);
this.application = application;
this.onVisitEnd = onVisitEnd;
}
@Override
public void visit(String name, Object value) {
addElement(name, getDexValue(value));
}
@Override
public void visitEnum(String name, String desc, String value) {
DexType owner = application.getTypeFromDescriptor(desc);
addElement(name, new DexValueEnum(application.getField(owner, value, desc)));
}
@Override
public AnnotationVisitor visitAnnotation(String name, String desc) {
return new CreateAnnotationVisitor(application, (names, values) ->
addElement(name, new DexValueAnnotation(
createEncodedAnnotation(desc, names, values, application))));
}
@Override
public AnnotationVisitor visitArray(String name) {
return new CreateAnnotationVisitor(application, (names, values) -> {
assert names == null;
addElement(name, new DexValueArray(values.toArray(DexValue.EMPTY_ARRAY)));
});
}
@Override
public void visitEnd() {
onVisitEnd.accept(names, values);
}
private void addElement(String name, DexValue value) {
if (name != null) {
if (names == null) {
names = new ArrayList<>();
}
names.add(application.getString(name));
}
values.add(value);
}
private static DexValueArray getDexValueArray(Object value) {
if (value instanceof byte[]) {
byte[] values = (byte[]) value;
DexValue[] elements = new DexValue[values.length];
for (int i = 0; i < values.length; i++) {
elements[i] = DexValueByte.create(values[i]);
}
return new DexValueArray(elements);
} else if (value instanceof boolean[]) {
boolean[] values = (boolean[]) value;
DexValue[] elements = new DexValue[values.length];
for (int i = 0; i < values.length; i++) {
elements[i] = DexValueBoolean.create(values[i]);
}
return new DexValueArray(elements);
} else if (value instanceof char[]) {
char[] values = (char[]) value;
DexValue[] elements = new DexValue[values.length];
for (int i = 0; i < values.length; i++) {
elements[i] = DexValueChar.create(values[i]);
}
return new DexValueArray(elements);
} else if (value instanceof short[]) {
short[] values = (short[]) value;
DexValue[] elements = new DexValue[values.length];
for (int i = 0; i < values.length; i++) {
elements[i] = DexValueShort.create(values[i]);
}
return new DexValueArray(elements);
} else if (value instanceof int[]) {
int[] values = (int[]) value;
DexValue[] elements = new DexValue[values.length];
for (int i = 0; i < values.length; i++) {
elements[i] = DexValueInt.create(values[i]);
}
return new DexValueArray(elements);
} else if (value instanceof long[]) {
long[] values = (long[]) value;
DexValue[] elements = new DexValue[values.length];
for (int i = 0; i < values.length; i++) {
elements[i] = DexValueLong.create(values[i]);
}
return new DexValueArray(elements);
} else if (value instanceof float[]) {
float[] values = (float[]) value;
DexValue[] elements = new DexValue[values.length];
for (int i = 0; i < values.length; i++) {
elements[i] = DexValueFloat.create(values[i]);
}
return new DexValueArray(elements);
} else if (value instanceof double[]) {
double[] values = (double[]) value;
DexValue[] elements = new DexValue[values.length];
for (int i = 0; i < values.length; i++) {
elements[i] = DexValueDouble.create(values[i]);
}
return new DexValueArray(elements);
}
throw new Unreachable("Unexpected type of annotation value: " + value);
}
private DexValue getDexValue(Object value) {
if (value == null) {
return DexValueNull.NULL;
}
if (value instanceof Byte) {
return DexValueByte.create((Byte) value);
} else if (value instanceof Boolean) {
return DexValueBoolean.create((Boolean) value);
} else if (value instanceof Character) {
return DexValueChar.create((Character) value);
} else if (value instanceof Short) {
return DexValueShort.create((Short) value);
} else if (value instanceof Integer) {
return DexValueInt.create((Integer) value);
} else if (value instanceof Long) {
return DexValueLong.create((Long) value);
} else if (value instanceof Float) {
return DexValueFloat.create((Float) value);
} else if (value instanceof Double) {
return DexValueDouble.create((Double) value);
} else if (value instanceof String) {
return new DexValueString(application.getString((String) value));
} else if (value instanceof Type) {
return new DexValueType(application.getTypeFromDescriptor(((Type) value).getDescriptor()));
}
return getDexValueArray(value);
}
}
public static class ReparseContext {
// This will hold the content of the whole class. Once all the methods of the class are swapped
// from this to the actual JarCode, no other references would be left and the content can be
// GC'd.
public byte[] classCache;
public DexClass owner;
public final List<Code> codeList = new ArrayList<>();
}
}