blob: 6794a77a6745f65cf474738d57451732d79e4528 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.bytecode;
import static org.objectweb.asm.ClassReader.EXPAND_FRAMES;
import static org.objectweb.asm.Opcodes.ACC_ABSTRACT;
import static org.objectweb.asm.Opcodes.ACC_INTERFACE;
import static org.objectweb.asm.Opcodes.ASM7;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
/**
* This ClassVisitor verifies that a class and its methods are suitable for rewriting.
* Given a class and a list of methods it performs the following checks:
* 1. Class is subclass of a class that we want to trace.
* 2. Class is not abstract or an interface.
*
* For each method provided in {@code methodsToCheck}:
* If the class overrides the method then we can rewrite it directly.
* If the class doesn't override the method then we can generate an override with {@link
* EmptyOverrideGeneratorClassAdapter}, but first we must check if the parent method is private or
* final using {@link ParentMethodCheckerClassAdapter}.
*
* This adapter modifies the provided method list to indicate which methods should be overridden or
* skipped.
*/
class MethodCheckerClassAdapter extends ClassVisitor {
private static final String VIEW_CLASS_DESCRIPTOR = "android/view/View";
private static final String ANIMATOR_UPDATE_LISTENER_CLASS_DESCRIPTOR =
"android/animation/ValueAnimator$AnimatorUpdateListener";
private static final String ANIMATOR_LISTENER_CLASS_DESCRIPTOR =
"android/animation/Animator$AnimatorListener";
private final ArrayList<MethodDescription> mMethodsToCheck;
private final ClassLoader mJarClassLoader;
private String mSuperName;
public MethodCheckerClassAdapter(
ArrayList<MethodDescription> methodsToCheck, ClassLoader jarClassLoader) {
super(ASM7);
mMethodsToCheck = methodsToCheck;
mJarClassLoader = jarClassLoader;
}
@Override
public void visit(int version, int access, String name, String signature, String superName,
String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
mSuperName = superName;
boolean isAbstract = (access & ACC_ABSTRACT) == ACC_ABSTRACT;
boolean isInterface = (access & ACC_INTERFACE) == ACC_INTERFACE;
if (isAbstract || isInterface || !shouldTraceClass(name)) {
mMethodsToCheck.clear();
return;
}
}
@Override
public MethodVisitor visitMethod(
int access, String name, String descriptor, String signature, String[] exceptions) {
if (mMethodsToCheck.isEmpty()) {
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
for (MethodDescription method : mMethodsToCheck) {
if (method.methodName.equals(name) && method.description.equals(descriptor)) {
method.shouldCreateOverride = false;
}
}
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
@Override
public void visitEnd() {
if (mMethodsToCheck.isEmpty()) {
super.visitEnd();
return;
}
boolean areAnyUncheckedMethods = false;
for (MethodDescription method : mMethodsToCheck) {
if (method.shouldCreateOverride == null) {
areAnyUncheckedMethods = true;
break;
}
}
if (areAnyUncheckedMethods) {
checkParentClass(mSuperName, mMethodsToCheck, mJarClassLoader);
}
super.visitEnd();
}
private boolean shouldTraceClass(String desc) {
Class clazz = getClass(desc);
return isClassDerivedFrom(clazz, VIEW_CLASS_DESCRIPTOR)
|| isClassDerivedFrom(clazz, ANIMATOR_UPDATE_LISTENER_CLASS_DESCRIPTOR)
|| isClassDerivedFrom(clazz, ANIMATOR_LISTENER_CLASS_DESCRIPTOR);
}
private boolean isClassDerivedFrom(Class clazz, String classDescriptor) {
Class superClass = getClass(classDescriptor);
if (clazz == null || superClass == null) return false;
return superClass.isAssignableFrom(clazz);
}
private Class getClass(String desc) {
try {
return mJarClassLoader.loadClass(desc.replace('/', '.'));
} catch (ClassNotFoundException | NoClassDefFoundError | IllegalAccessError e) {
return null;
}
}
static void checkParentClass(String superClassName, ArrayList<MethodDescription> methodsToCheck,
ClassLoader jarClassLoader) {
try {
ClassReader cr = new ClassReader(getClassAsStream(jarClassLoader, superClassName));
ParentMethodCheckerClassAdapter parentChecker =
new ParentMethodCheckerClassAdapter(methodsToCheck, jarClassLoader);
cr.accept(parentChecker, EXPAND_FRAMES);
} catch (IOException ex) {
// Ignore errors in case class can't be loaded.
}
}
private static InputStream getClassAsStream(ClassLoader jarClassLoader, String desc) {
return jarClassLoader.getResourceAsStream(desc.replace('.', '/') + ".class");
}
}