blob: 4a8515951f487417bc658c8290d1fd89782e7ef9 [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.util.ArrayList;
import java.util.Arrays;
/**
* 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;
public static void main(String[] args) throws IOException {
// Invoke this script using //build/android/gyp/trace_event_bytecode_rewriter.py
if (args.length < 2) {
System.err.println("Expected arguments: <':' separated list with N input jar paths> "
+ "<':' separated list with N output jar paths>");
System.exit(1);
}
String[] inputJars = args[0].split(":");
String[] outputJars = args[1].split(":");
assert inputJars.length
== outputJars.length : "Input and output lists are not the same length. Inputs: "
+ 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 < inputJars.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"));
assert outputJarPath.getName().startsWith(inputFilenameNoExtension);
}
ArrayList<String> classPathJarsPaths = new ArrayList<>();
classPathJarsPaths.addAll(Arrays.asList(inputJars));
ClassLoader classPathJarsClassLoader = ByteCodeProcessor.loadJars(classPathJarsPaths);
TraceEventAdder adder = new TraceEventAdder(classPathJarsClassLoader);
for (int i = 0; i < inputJars.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;
}
}