blob: 81ebecc725a396002f6296155daa6cbf95fc8cc0 [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 org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Opcodes;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
/**
* Java application that modifies all implementations of "draw", "onMeasure" and "onLayout" on all
* {@link android.view.View} subclasses to wrap them in trace events.
*/
public class TraceEventAdder extends ByteCodeRewriter {
private final ClassLoader mClassPathJarsClassLoader;
private ArrayList<MethodDescription> mMethodsToTrace;
/**
* Loads a list of jars and returns a ClassLoader capable of loading all classes found in the
* given jars.
*/
static ClassLoader loadJars(Collection<String> paths) {
URL[] jarUrls = new URL[paths.size()];
int i = 0;
for (String path : paths) {
try {
jarUrls[i++] = new File(path).toURI().toURL();
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
return new URLClassLoader(jarUrls);
}
public static void main(String[] args) throws IOException {
// Invoke this script using //build/android/gyp/trace_event_bytecode_rewriter.py
args = ByteCodeRewriter.expandArgs(args);
if (args.length < 2) {
System.err.println(
"Expected arguments: <':' separated list of input jar paths> "
+ "<':' separated list of output jar paths>");
System.exit(1);
}
String[] inputJars = args[0].split(":");
String[] outputJars = args[1].split(":");
assert inputJars.length >= outputJars.length
: "Input list must be a superset of the output list, where the "
+ "first N entries match, and N is the length of the output list."
+ inputJars.length
+ " Outputs: "
+ outputJars.length;
// outputJars[n] must be the same as inputJars[n] but with a suffix, validate this.
for (int i = 0; i < outputJars.length; i++) {
File inputJarPath = new File(inputJars[i]);
String inputJarFilename = inputJarPath.getName();
File outputJarPath = new File(outputJars[i]);
String inputFilenameNoExtension =
inputJarFilename.substring(0, inputJarFilename.lastIndexOf(".jar"));
// Ensuring that for every output, we have the matching input.
assert outputJarPath.getName().startsWith(inputFilenameNoExtension);
}
ArrayList<String> classPathJarsPaths = new ArrayList<>();
classPathJarsPaths.addAll(Arrays.asList(inputJars));
ClassLoader classPathJarsClassLoader = loadJars(classPathJarsPaths);
TraceEventAdder adder = new TraceEventAdder(classPathJarsClassLoader);
for (int i = 0; i < outputJars.length; i++) {
adder.rewrite(new File(inputJars[i]), new File(outputJars[i]));
}
}
public TraceEventAdder(ClassLoader classPathJarsClassLoader) {
mClassPathJarsClassLoader = classPathJarsClassLoader;
}
@Override
protected boolean shouldRewriteClass(String classPath) {
return true;
}
@Override
protected boolean shouldRewriteClass(ClassReader classReader) {
mMethodsToTrace =
new ArrayList<>(
Arrays.asList(
// Methods on View.java
new MethodDescription(
"dispatchTouchEvent",
"(Landroid/view/MotionEvent;)Z",
Opcodes.ACC_PUBLIC),
new MethodDescription(
"draw", "(Landroid/graphics/Canvas;)V", Opcodes.ACC_PUBLIC),
new MethodDescription("onMeasure", "(II)V", Opcodes.ACC_PROTECTED),
new MethodDescription(
"onLayout", "(ZIIII)V", Opcodes.ACC_PROTECTED),
// Methods on RecyclerView.java in AndroidX
new MethodDescription("scrollStep", "(II[I)V", 0),
// Methods on Animator.AnimatorListener
new MethodDescription(
"onAnimationStart",
"(Landroid/animation/Animator;)V",
Opcodes.ACC_PUBLIC),
new MethodDescription(
"onAnimationEnd",
"(Landroid/animation/Animator;)V",
Opcodes.ACC_PUBLIC),
// Methods on ValueAnimator.AnimatorUpdateListener
new MethodDescription(
"onAnimationUpdate",
"(Landroid/animation/ValueAnimator;)V",
Opcodes.ACC_PUBLIC)));
// This adapter will modify mMethodsToTrace to indicate which methods already exist in the
// class and which ones need to be overridden. In case the class is not an Android view
// we'll clear the list and skip rewriting.
MethodCheckerClassAdapter methodChecker =
new MethodCheckerClassAdapter(mMethodsToTrace, mClassPathJarsClassLoader);
classReader.accept(methodChecker, ClassReader.EXPAND_FRAMES);
return !mMethodsToTrace.isEmpty();
}
@Override
protected ClassVisitor getClassVisitorForClass(String classPath, ClassVisitor delegate) {
ClassVisitor chain = new TraceEventAdderClassAdapter(delegate, mMethodsToTrace);
chain = new EmptyOverrideGeneratorClassAdapter(chain, mMethodsToTrace);
return chain;
}
}