summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/build/android/bytecode
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/libwebrtc/build/android/bytecode
parentInitial commit. (diff)
downloadfirefox-esr-upstream.tar.xz
firefox-esr-upstream.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/build/android/bytecode')
-rw-r--r--third_party/libwebrtc/build/android/bytecode/BUILD.gn86
-rw-r--r--third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ByteCodeProcessor.java167
-rw-r--r--third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ByteCodeRewriter.java101
-rw-r--r--third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ClassPathValidator.java233
-rw-r--r--third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/EmptyOverrideGeneratorClassAdapter.java103
-rw-r--r--third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/FragmentActivityReplacer.java238
-rw-r--r--third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/MethodCheckerClassAdapter.java136
-rw-r--r--third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/MethodDescription.java20
-rw-r--r--third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ParentMethodCheckerClassAdapter.java94
-rw-r--r--third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TraceEventAdder.java87
-rw-r--r--third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TraceEventAdderClassAdapter.java47
-rw-r--r--third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TraceEventAdderMethodAdapter.java83
-rw-r--r--third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TypeUtils.java87
13 files changed, 1482 insertions, 0 deletions
diff --git a/third_party/libwebrtc/build/android/bytecode/BUILD.gn b/third_party/libwebrtc/build/android/bytecode/BUILD.gn
new file mode 100644
index 0000000000..8d717eb0ad
--- /dev/null
+++ b/third_party/libwebrtc/build/android/bytecode/BUILD.gn
@@ -0,0 +1,86 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/android/rules.gni")
+
+java_binary("bytecode_processor") {
+ main_class = "org.chromium.bytecode.ByteCodeProcessor"
+ wrapper_script_name = "helper/bytecode_processor"
+ deps = [ ":bytecode_processor_java" ]
+}
+
+java_library("bytecode_processor_java") {
+ sources = [
+ "java/org/chromium/bytecode/ByteCodeProcessor.java",
+ "java/org/chromium/bytecode/ClassPathValidator.java",
+ "java/org/chromium/bytecode/TypeUtils.java",
+ ]
+ deps = [
+ "//third_party/android_deps:org_ow2_asm_asm_java",
+ "//third_party/android_deps:org_ow2_asm_asm_util_java",
+ ]
+ enable_bytecode_checks = false
+}
+
+# A bytecode rewriter that replaces all calls to
+# `FragmentActivity Fragment.getActivity()` with
+# `Activity Fragment.getActivity()`.
+java_binary("fragment_activity_replacer") {
+ main_class = "org.chromium.bytecode.FragmentActivityReplacer"
+ deps = [ ":fragment_activity_replacer_java" ]
+ wrapper_script_name = "helper/fragment_activity_replacer"
+}
+
+# A bytecode rewriter that replaces all calls to
+# `FragmentActivity Fragment.getActivity()` with
+# `Activity Fragment.getActivity()` followed by a cast to FragmentActivity.
+# Prefer :fragment_activity_replacer. This rewriter should only be used for
+# libraries that rely on getActivity() returning a FragmentActivity *and* are
+# not going to be used in an app that contains multiple copies of the AndroidX
+# Fragment library (i.e. WebLayer).
+java_binary("fragment_activity_replacer_single_androidx") {
+ main_class = "org.chromium.bytecode.FragmentActivityReplacer"
+ deps = [ ":fragment_activity_replacer_java" ]
+ wrapper_script_name = "helper/fragment_activity_replacer_single_androidx"
+ wrapper_script_args = [ "--single-androidx" ]
+}
+
+java_library("fragment_activity_replacer_java") {
+ visibility = [ ":*" ]
+ sources = [
+ "java/org/chromium/bytecode/ByteCodeRewriter.java",
+ "java/org/chromium/bytecode/FragmentActivityReplacer.java",
+ ]
+ deps = [
+ "//third_party/android_deps:org_ow2_asm_asm_commons_java",
+ "//third_party/android_deps:org_ow2_asm_asm_java",
+ "//third_party/android_deps:org_ow2_asm_asm_util_java",
+ ]
+}
+
+java_binary("trace_event_adder") {
+ main_class = "org.chromium.bytecode.TraceEventAdder"
+ deps = [ ":trace_event_adder_java" ]
+ wrapper_script_name = "helper/trace_event_adder"
+}
+
+java_library("trace_event_adder_java") {
+ visibility = [ ":*" ]
+ sources = [
+ "java/org/chromium/bytecode/ByteCodeRewriter.java",
+ "java/org/chromium/bytecode/EmptyOverrideGeneratorClassAdapter.java",
+ "java/org/chromium/bytecode/MethodCheckerClassAdapter.java",
+ "java/org/chromium/bytecode/MethodDescription.java",
+ "java/org/chromium/bytecode/ParentMethodCheckerClassAdapter.java",
+ "java/org/chromium/bytecode/TraceEventAdder.java",
+ "java/org/chromium/bytecode/TraceEventAdderClassAdapter.java",
+ "java/org/chromium/bytecode/TraceEventAdderMethodAdapter.java",
+ ]
+ deps = [
+ ":bytecode_processor_java",
+ "//third_party/android_deps:org_ow2_asm_asm_commons_java",
+ "//third_party/android_deps:org_ow2_asm_asm_java",
+ "//third_party/android_deps:org_ow2_asm_asm_util_java",
+ ]
+}
diff --git a/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ByteCodeProcessor.java b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ByteCodeProcessor.java
new file mode 100644
index 0000000000..b767f4f089
--- /dev/null
+++ b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ByteCodeProcessor.java
@@ -0,0 +1,167 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// 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 java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+/**
+ * Java application that takes in an input jar, performs a series of bytecode
+ * transformations, and generates an output jar.
+ */
+class ByteCodeProcessor {
+ private static final String CLASS_FILE_SUFFIX = ".class";
+ private static final int BUFFER_SIZE = 16384;
+ private static boolean sVerbose;
+ private static boolean sIsPrebuilt;
+ private static ClassLoader sDirectClassPathClassLoader;
+ private static ClassLoader sFullClassPathClassLoader;
+ private static Set<String> sFullClassPathJarPaths;
+ private static Set<String> sMissingClassesAllowlist;
+ private static Map<String, String> sJarToGnTarget;
+ private static ClassPathValidator sValidator;
+
+ private static Void processEntry(ZipEntry entry, byte[] data) {
+ ClassReader reader = new ClassReader(data);
+ if (sIsPrebuilt) {
+ sValidator.validateFullClassPath(
+ reader, sFullClassPathClassLoader, sMissingClassesAllowlist);
+ } else {
+ sValidator.validateDirectClassPath(reader, sDirectClassPathClassLoader,
+ sFullClassPathClassLoader, sFullClassPathJarPaths, sMissingClassesAllowlist,
+ sVerbose);
+ }
+ return null;
+ }
+
+ private static void process(String gnTarget, String inputJarPath)
+ throws ExecutionException, InterruptedException {
+ ExecutorService executorService =
+ Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
+ try (ZipInputStream inputStream = new ZipInputStream(
+ new BufferedInputStream(new FileInputStream(inputJarPath)))) {
+ while (true) {
+ ZipEntry entry = inputStream.getNextEntry();
+ if (entry == null) {
+ break;
+ }
+ byte[] data = readAllBytes(inputStream);
+ executorService.submit(() -> processEntry(entry, data));
+ }
+ executorService.shutdown(); // This is essential in order to avoid waiting infinitely.
+ executorService.awaitTermination(1, TimeUnit.HOURS);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ if (sValidator.hasErrors()) {
+ sValidator.printAll(gnTarget, sJarToGnTarget);
+ System.exit(1);
+ }
+ }
+
+ private static byte[] readAllBytes(InputStream inputStream) throws IOException {
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ int numRead = 0;
+ byte[] data = new byte[BUFFER_SIZE];
+ while ((numRead = inputStream.read(data, 0, data.length)) != -1) {
+ buffer.write(data, 0, numRead);
+ }
+ return buffer.toByteArray();
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Extracts a length-encoded list of strings from the arguments, and adds them to |out|. Returns
+ * the new "next index" to be processed.
+ */
+ private static int parseListArgument(String[] args, int index, Collection<String> out) {
+ int argLength = Integer.parseInt(args[index++]);
+ out.addAll(Arrays.asList(Arrays.copyOfRange(args, index, index + argLength)));
+ return index + argLength;
+ }
+
+ public static void main(String[] args) throws ClassPathValidator.ClassNotLoadedException,
+ ExecutionException, InterruptedException {
+ // Invoke this script using //build/android/gyp/bytecode_processor.py
+ int currIndex = 0;
+ String gnTarget = args[currIndex++];
+ String inputJarPath = args[currIndex++];
+ sVerbose = args[currIndex++].equals("--verbose");
+ sIsPrebuilt = args[currIndex++].equals("--is-prebuilt");
+
+ sMissingClassesAllowlist = new HashSet<>();
+ currIndex = parseListArgument(args, currIndex, sMissingClassesAllowlist);
+
+ ArrayList<String> sdkJarPaths = new ArrayList<>();
+ currIndex = parseListArgument(args, currIndex, sdkJarPaths);
+
+ ArrayList<String> directClassPathJarPaths = new ArrayList<>();
+ directClassPathJarPaths.add(inputJarPath);
+ directClassPathJarPaths.addAll(sdkJarPaths);
+ currIndex = parseListArgument(args, currIndex, directClassPathJarPaths);
+ sDirectClassPathClassLoader = loadJars(directClassPathJarPaths);
+
+ ArrayList<String> fullClassPathJarPaths = new ArrayList<>();
+ currIndex = parseListArgument(args, currIndex, fullClassPathJarPaths);
+ ArrayList<String> gnTargets = new ArrayList<>();
+ parseListArgument(args, currIndex, gnTargets);
+ sJarToGnTarget = new HashMap<>();
+ assert fullClassPathJarPaths.size() == gnTargets.size();
+ for (int i = 0; i < fullClassPathJarPaths.size(); ++i) {
+ sJarToGnTarget.put(fullClassPathJarPaths.get(i), gnTargets.get(i));
+ }
+
+ // Load all jars that are on the classpath for the input jar for analyzing class
+ // hierarchy.
+ sFullClassPathJarPaths = new HashSet<>();
+ sFullClassPathJarPaths.add(inputJarPath);
+ sFullClassPathJarPaths.addAll(sdkJarPaths);
+ sFullClassPathJarPaths.addAll(fullClassPathJarPaths);
+ sFullClassPathClassLoader = loadJars(sFullClassPathJarPaths);
+ sFullClassPathJarPaths.removeAll(directClassPathJarPaths);
+
+ sValidator = new ClassPathValidator();
+ process(gnTarget, inputJarPath);
+ }
+}
diff --git a/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ByteCodeRewriter.java b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ByteCodeRewriter.java
new file mode 100644
index 0000000000..37b0e86348
--- /dev/null
+++ b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ByteCodeRewriter.java
@@ -0,0 +1,101 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// 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.ClassWriter;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Base class for scripts that perform bytecode modifications on a jar file.
+ */
+public abstract class ByteCodeRewriter {
+ private static final String CLASS_FILE_SUFFIX = ".class";
+
+ public void rewrite(File inputJar, File outputJar) throws IOException {
+ if (!inputJar.exists()) {
+ throw new FileNotFoundException("Input jar not found: " + inputJar.getPath());
+ }
+ try (InputStream inputStream = new BufferedInputStream(new FileInputStream(inputJar))) {
+ try (OutputStream outputStream = new FileOutputStream(outputJar)) {
+ processZip(inputStream, outputStream);
+ }
+ }
+ }
+
+ /** Returns true if the class at the given path in the archive should be rewritten. */
+ protected abstract boolean shouldRewriteClass(String classPath);
+
+ /**
+ * Returns true if the class at the given {@link ClassReader} should be rewritten.
+ */
+ protected boolean shouldRewriteClass(ClassReader classReader) {
+ return true;
+ }
+
+ /**
+ * Returns the ClassVisitor that should be used to modify the bytecode of class at the given
+ * path in the archive.
+ */
+ protected abstract ClassVisitor getClassVisitorForClass(
+ String classPath, ClassVisitor delegate);
+
+ private void processZip(InputStream inputStream, OutputStream outputStream) {
+ try (ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) {
+ ZipInputStream zipInputStream = new ZipInputStream(inputStream);
+ ZipEntry entry;
+ while ((entry = zipInputStream.getNextEntry()) != null) {
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ boolean handled = processClassEntry(entry, zipInputStream, buffer);
+ if (handled) {
+ ZipEntry newEntry = new ZipEntry(entry.getName());
+ zipOutputStream.putNextEntry(newEntry);
+ zipOutputStream.write(buffer.toByteArray(), 0, buffer.size());
+ } else {
+ zipOutputStream.putNextEntry(entry);
+ zipInputStream.transferTo(zipOutputStream);
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private boolean processClassEntry(
+ ZipEntry entry, InputStream inputStream, OutputStream outputStream) {
+ if (!entry.getName().endsWith(CLASS_FILE_SUFFIX) || !shouldRewriteClass(entry.getName())) {
+ return false;
+ }
+ try {
+ ClassReader reader = new ClassReader(inputStream);
+ ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES);
+ ClassVisitor classVisitor = writer;
+ if (shouldRewriteClass(reader)) {
+ classVisitor = getClassVisitorForClass(entry.getName(), writer);
+ }
+ reader.accept(classVisitor, ClassReader.EXPAND_FRAMES);
+
+ writer.visitEnd();
+ byte[] classData = writer.toByteArray();
+ outputStream.write(classData, 0, classData.length);
+ return true;
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ClassPathValidator.java b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ClassPathValidator.java
new file mode 100644
index 0000000000..9f45df5117
--- /dev/null
+++ b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ClassPathValidator.java
@@ -0,0 +1,233 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// 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 java.io.PrintStream;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.function.Consumer;
+
+/**
+ * Checks classpaths (given as ClassLoaders) by reading the constant pool of the class file and
+ * attempting to load every referenced class. If there are some that are unable to be found, it
+ * stores a helpful error message if it knows where it might find them, and exits the program if it
+ * can't find the class with any given classpath.
+ */
+public class ClassPathValidator {
+ // Number of warnings to print.
+ private static final int MAX_MISSING_CLASS_WARNINGS = 10;
+ // Number of missing classes to show per missing jar.
+ private static final int MAX_ERRORS_PER_JAR = 2;
+ // Map of missing .jar -> Missing class -> Classes that failed.
+ // TreeMap so that error messages have sorted list of jars.
+ private final Map<String, Map<String, Set<String>>> mDirectErrors =
+ Collections.synchronizedMap(new TreeMap<>());
+ // Missing classes we only track the first one for each jar.
+ // Map of missingClass -> srcClass.
+ private final Map<String, String> mMissingClasses =
+ Collections.synchronizedMap(new TreeMap<>());
+
+ static class ClassNotLoadedException extends ClassNotFoundException {
+ private final String mClassName;
+
+ ClassNotLoadedException(String className, Throwable ex) {
+ super("Couldn't load " + className, ex);
+ mClassName = className;
+ }
+
+ public String getClassName() {
+ return mClassName;
+ }
+ }
+
+ private static void validateClass(ClassLoader classLoader, String className)
+ throws ClassNotLoadedException {
+ if (className.startsWith("[")) {
+ // Dealing with an array type which isn't encoded nicely in the constant pool.
+ // For example, [[Lorg/chromium/Class$1;
+ className = className.substring(className.lastIndexOf('[') + 1);
+ if (className.charAt(0) == 'L' && className.endsWith(";")) {
+ className = className.substring(1, className.length() - 1);
+ } else {
+ // Bailing out if we have an non-class array type.
+ // This could be something like [B
+ return;
+ }
+ }
+ if (className.matches(".*\\bR(\\$\\w+)?$")) {
+ // Resources in R.java files are not expected to be valid at this stage in the build.
+ return;
+ }
+ if (className.matches("^libcore\\b.*")) {
+ // libcore exists on devices, but is not included in the Android sdk as it is a private
+ // API.
+ return;
+ }
+ try {
+ classLoader.loadClass(className.replace('/', '.'));
+ } catch (ClassNotFoundException e) {
+ throw new ClassNotLoadedException(className, e);
+ } catch (NoClassDefFoundError e) {
+ // We assume that this is caused by another class that is not going to able to be
+ // loaded, so we will skip this and let that class fail with ClassNotFoundException.
+ }
+ }
+
+ /**
+ * Given a .class file, see if every class referenced in the main class' constant pool can be
+ * loaded by the given ClassLoader.
+ *
+ * @param classReader .class file interface for reading the constant pool.
+ * @param classLoader classpath you wish to validate.
+ * @param errorConsumer Called for each missing class.
+ */
+ private static void validateClassPath(ClassReader classReader, ClassLoader classLoader,
+ Consumer<ClassNotLoadedException> errorConsumer) {
+ char[] charBuffer = new char[classReader.getMaxStringLength()];
+ // According to the Java spec, the constant pool is indexed from 1 to constant_pool_count -
+ // 1. See https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4
+ for (int i = 1; i < classReader.getItemCount(); i++) {
+ int offset = classReader.getItem(i);
+ // Class entries correspond to 7 in the constant pool
+ // https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4
+ if (offset > 0 && classReader.readByte(offset - 1) == 7) {
+ try {
+ validateClass(classLoader, classReader.readUTF8(offset, charBuffer));
+ } catch (ClassNotLoadedException e) {
+ errorConsumer.accept(e);
+ }
+ }
+ }
+ }
+
+ public void validateFullClassPath(ClassReader classReader, ClassLoader fullClassLoader,
+ Set<String> missingClassAllowlist) {
+ // Prebuilts only need transitive dependencies checked, not direct dependencies.
+ validateClassPath(classReader, fullClassLoader, (e) -> {
+ if (!missingClassAllowlist.contains(e.getClassName())) {
+ addMissingError(classReader.getClassName(), e.getClassName());
+ }
+ });
+ }
+
+ public void validateDirectClassPath(ClassReader classReader, ClassLoader directClassLoader,
+ ClassLoader fullClassLoader, Collection<String> jarsOnlyInFullClassPath,
+ Set<String> missingClassAllowlist, boolean verbose) {
+ validateClassPath(classReader, directClassLoader, (e) -> {
+ try {
+ validateClass(fullClassLoader, e.getClassName());
+ } catch (ClassNotLoadedException d) {
+ if (!missingClassAllowlist.contains(e.getClassName())) {
+ addMissingError(classReader.getClassName(), e.getClassName());
+ }
+ return;
+ }
+ if (verbose) {
+ System.err.println("Class \"" + e.getClassName()
+ + "\" not found in direct dependencies,"
+ + " but found in indirect dependiences.");
+ }
+ // Iterating through all jars that are in the full classpath but not the direct
+ // classpath to find which one provides the class we are looking for.
+ for (String jarPath : jarsOnlyInFullClassPath) {
+ try {
+ ClassLoader smallLoader =
+ ByteCodeProcessor.loadJars(Collections.singletonList(jarPath));
+ validateClass(smallLoader, e.getClassName());
+ addDirectError(jarPath, classReader.getClassName(), e.getClassName());
+ break;
+ } catch (ClassNotLoadedException f) {
+ }
+ }
+ });
+ }
+
+ private void addMissingError(String srcClass, String missingClass) {
+ mMissingClasses.put(missingClass, srcClass);
+ }
+
+ private void addDirectError(String jarPath, String srcClass, String missingClass) {
+ synchronized (mDirectErrors) {
+ Map<String, Set<String>> failedClassesByMissingClass = mDirectErrors.get(jarPath);
+ if (failedClassesByMissingClass == null) {
+ // TreeMap so that error messages have sorted list of classes.
+ failedClassesByMissingClass = new TreeMap<>();
+ mDirectErrors.put(jarPath, failedClassesByMissingClass);
+ }
+ Set<String> failedClasses = failedClassesByMissingClass.get(missingClass);
+ if (failedClasses == null) {
+ failedClasses = new TreeSet<>();
+ failedClassesByMissingClass.put(missingClass, failedClasses);
+ }
+ failedClasses.add(srcClass);
+ }
+ }
+
+ public boolean hasErrors() {
+ return !mDirectErrors.isEmpty() || !mMissingClasses.isEmpty();
+ }
+
+ private static void printValidationError(
+ PrintStream out, String gnTarget, Map<String, Set<String>> missingClasses) {
+ out.print(" * ");
+ out.println(gnTarget);
+ int i = 0;
+ // The list of missing classes is non-exhaustive because each class that fails to validate
+ // reports only the first missing class.
+ for (Map.Entry<String, Set<String>> entry : missingClasses.entrySet()) {
+ String missingClass = entry.getKey();
+ Set<String> filesThatNeededIt = entry.getValue();
+ out.print(" * ");
+ if (i == MAX_ERRORS_PER_JAR) {
+ out.print(String.format(
+ "And %d more...", missingClasses.size() - MAX_ERRORS_PER_JAR));
+ break;
+ }
+ out.print(missingClass.replace('/', '.'));
+ out.print(" (needed by ");
+ out.print(filesThatNeededIt.iterator().next().replace('/', '.'));
+ if (filesThatNeededIt.size() > 1) {
+ out.print(String.format(" and %d more", filesThatNeededIt.size() - 1));
+ }
+ out.println(")");
+ i++;
+ }
+ }
+
+ public void printAll(String gnTarget, Map<String, String> jarToGnTarget) {
+ String streamer = "=============================";
+ System.err.println();
+ System.err.println(streamer + " Dependency Checks Failed " + streamer);
+ System.err.println("Target: " + gnTarget);
+ if (!mMissingClasses.isEmpty()) {
+ int i = 0;
+ for (Map.Entry<String, String> entry : mMissingClasses.entrySet()) {
+ if (++i > MAX_MISSING_CLASS_WARNINGS) {
+ System.err.println(String.format("... and %d more.",
+ mMissingClasses.size() - MAX_MISSING_CLASS_WARNINGS));
+ break;
+ }
+ System.err.println(String.format(
+ "Class \"%s\" not found on any classpath. Used by class \"%s\"",
+ entry.getKey(), entry.getValue()));
+ }
+ System.err.println();
+ }
+ if (!mDirectErrors.isEmpty()) {
+ System.err.println("Direct classpath is incomplete. To fix, add deps on:");
+ for (Map.Entry<String, Map<String, Set<String>>> entry : mDirectErrors.entrySet()) {
+ printValidationError(
+ System.err, jarToGnTarget.get(entry.getKey()), entry.getValue());
+ }
+ System.err.println();
+ }
+ }
+}
diff --git a/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/EmptyOverrideGeneratorClassAdapter.java b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/EmptyOverrideGeneratorClassAdapter.java
new file mode 100644
index 0000000000..d0957625d7
--- /dev/null
+++ b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/EmptyOverrideGeneratorClassAdapter.java
@@ -0,0 +1,103 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// 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.Opcodes.ACC_ABSTRACT;
+import static org.objectweb.asm.Opcodes.ACC_INTERFACE;
+import static org.objectweb.asm.Opcodes.ALOAD;
+import static org.objectweb.asm.Opcodes.ASM7;
+import static org.objectweb.asm.Opcodes.ILOAD;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.IRETURN;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Type;
+
+import java.util.ArrayList;
+
+class EmptyOverrideGeneratorClassAdapter extends ClassVisitor {
+ private final ArrayList<MethodDescription> mMethodsToGenerate;
+ private String mSuperClassName;
+ private boolean mIsAbstract;
+ private boolean mIsInterface;
+
+ public EmptyOverrideGeneratorClassAdapter(
+ ClassVisitor cv, ArrayList<MethodDescription> methodsToGenerate) {
+ super(ASM7, cv);
+ mMethodsToGenerate = methodsToGenerate;
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName,
+ String[] interfaces) {
+ super.visit(version, access, name, signature, superName, interfaces);
+
+ mSuperClassName = superName;
+ mIsAbstract = (access & ACC_ABSTRACT) == ACC_ABSTRACT;
+ mIsInterface = (access & ACC_INTERFACE) == ACC_INTERFACE;
+ }
+
+ @Override
+ public void visitEnd() {
+ if (mIsAbstract || mIsInterface || mMethodsToGenerate.isEmpty()) {
+ super.visitEnd();
+ return;
+ }
+
+ for (MethodDescription method : mMethodsToGenerate) {
+ if (!method.shouldCreateOverride) {
+ continue;
+ }
+
+ MethodVisitor mv = super.visitMethod(
+ method.access, method.methodName, method.description, null, null);
+ writeOverrideCode(mv, method.access, method.methodName, method.description);
+ }
+
+ super.visitEnd();
+ }
+
+ /**
+ * Writes code to a method to call that method's parent implementation.
+ * <pre>
+ * {@code
+ * // Calling writeOverrideCode(mv, ACC_PUBLIC, "doFoo", "(Ljava/lang/String;)I") writes the
+ * following method body: public int doFoo(String arg){ return super.doFoo(arg);
+ * }
+ * }
+ * </pre>
+ *
+ * This will be rewritten later by TraceEventAdderClassAdapter to wrap the body in a trace
+ * event.
+ */
+ private void writeOverrideCode(
+ MethodVisitor mv, final int access, final String name, final String descriptor) {
+ Type[] argTypes = Type.getArgumentTypes(descriptor);
+ Type returnType = Type.getReturnType(descriptor);
+
+ mv.visitCode();
+
+ // Variable 0 contains `this`, load it into the operand stack.
+ mv.visitVarInsn(ALOAD, 0);
+
+ // Variables 1..n contain all arguments, load them all into the operand stack.
+ int i = 1;
+ for (Type arg : argTypes) {
+ // getOpcode(ILOAD) returns the ILOAD equivalent to the current argument's type.
+ mv.visitVarInsn(arg.getOpcode(ILOAD), i);
+ i += arg.getSize();
+ }
+
+ // Call the parent class method with the same arguments.
+ mv.visitMethodInsn(INVOKESPECIAL, mSuperClassName, name, descriptor, false);
+
+ // Return the result.
+ mv.visitInsn(returnType.getOpcode(IRETURN));
+
+ mv.visitMaxs(0, 0);
+ mv.visitEnd();
+ }
+}
diff --git a/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/FragmentActivityReplacer.java b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/FragmentActivityReplacer.java
new file mode 100644
index 0000000000..a40f39c4ce
--- /dev/null
+++ b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/FragmentActivityReplacer.java
@@ -0,0 +1,238 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// 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.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.MethodRemapper;
+import org.objectweb.asm.commons.Remapper;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Java application that modifies Fragment.getActivity() to return an Activity instead of a
+ * FragmentActivity, and updates any existing getActivity() calls to reference the updated method.
+ *
+ * See crbug.com/1144345 for more context.
+ */
+public class FragmentActivityReplacer extends ByteCodeRewriter {
+ private static final String GET_ACTIVITY_METHOD_NAME = "getActivity";
+ private static final String GET_LIFECYCLE_ACTIVITY_METHOD_NAME = "getLifecycleActivity";
+ private static final String NEW_METHOD_DESCRIPTOR = "()Landroid/app/Activity;";
+ private static final String OLD_METHOD_DESCRIPTOR =
+ "()Landroidx/fragment/app/FragmentActivity;";
+ private static final String REQUIRE_ACTIVITY_METHOD_NAME = "requireActivity";
+ private static final String SUPPORT_LIFECYCLE_FRAGMENT_IMPL_BINARY_NAME =
+ "com.google.android.gms.common.api.internal.SupportLifecycleFragmentImpl";
+
+ public static void main(String[] args) throws IOException {
+ // Invoke this script using //build/android/gyp/bytecode_rewriter.py
+ if (!(args.length == 2 || args.length == 3 && args[0].equals("--single-androidx"))) {
+ System.err.println("Expected arguments: [--single-androidx] <input.jar> <output.jar>");
+ System.exit(1);
+ }
+
+ if (args.length == 2) {
+ FragmentActivityReplacer rewriter = new FragmentActivityReplacer(false);
+ rewriter.rewrite(new File(args[0]), new File(args[1]));
+ } else {
+ FragmentActivityReplacer rewriter = new FragmentActivityReplacer(true);
+ rewriter.rewrite(new File(args[1]), new File(args[2]));
+ }
+ }
+
+ private final boolean mSingleAndroidX;
+
+ public FragmentActivityReplacer(boolean singleAndroidX) {
+ mSingleAndroidX = singleAndroidX;
+ }
+
+ @Override
+ protected boolean shouldRewriteClass(String classPath) {
+ return true;
+ }
+
+ @Override
+ protected ClassVisitor getClassVisitorForClass(String classPath, ClassVisitor delegate) {
+ ClassVisitor invocationVisitor = new InvocationReplacer(delegate, mSingleAndroidX);
+ switch (classPath) {
+ case "androidx/fragment/app/Fragment.class":
+ return new FragmentClassVisitor(invocationVisitor);
+ case "com/google/android/gms/common/api/internal/SupportLifecycleFragmentImpl.class":
+ return new SupportLifecycleFragmentImplClassVisitor(invocationVisitor);
+ default:
+ return invocationVisitor;
+ }
+ }
+
+ /**
+ * Updates any Fragment.getActivity/requireActivity() or getLifecycleActivity() calls to call
+ * the replaced method.
+ */
+ private static class InvocationReplacer extends ClassVisitor {
+ private final boolean mSingleAndroidX;
+
+ private InvocationReplacer(ClassVisitor baseVisitor, boolean singleAndroidX) {
+ super(Opcodes.ASM7, baseVisitor);
+ mSingleAndroidX = singleAndroidX;
+ }
+
+ @Override
+ public MethodVisitor visitMethod(
+ int access, String name, String descriptor, String signature, String[] exceptions) {
+ MethodVisitor base = super.visitMethod(access, name, descriptor, signature, exceptions);
+ return new MethodVisitor(Opcodes.ASM7, base) {
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name,
+ String descriptor, boolean isInterface) {
+ boolean isFragmentGetActivity = name.equals(GET_ACTIVITY_METHOD_NAME)
+ && descriptor.equals(OLD_METHOD_DESCRIPTOR)
+ && isFragmentSubclass(owner);
+ boolean isFragmentRequireActivity = name.equals(REQUIRE_ACTIVITY_METHOD_NAME)
+ && descriptor.equals(OLD_METHOD_DESCRIPTOR)
+ && isFragmentSubclass(owner);
+ boolean isSupportLifecycleFragmentImplGetLifecycleActivity =
+ name.equals(GET_LIFECYCLE_ACTIVITY_METHOD_NAME)
+ && descriptor.equals(OLD_METHOD_DESCRIPTOR)
+ && owner.equals(SUPPORT_LIFECYCLE_FRAGMENT_IMPL_BINARY_NAME);
+ if ((opcode == Opcodes.INVOKEVIRTUAL || opcode == Opcodes.INVOKESPECIAL)
+ && (isFragmentGetActivity || isFragmentRequireActivity
+ || isSupportLifecycleFragmentImplGetLifecycleActivity)) {
+ super.visitMethodInsn(
+ opcode, owner, name, NEW_METHOD_DESCRIPTOR, isInterface);
+ if (mSingleAndroidX) {
+ super.visitTypeInsn(
+ Opcodes.CHECKCAST, "androidx/fragment/app/FragmentActivity");
+ }
+ } else {
+ super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ }
+ }
+
+ private boolean isFragmentSubclass(String internalType) {
+ // Look up classes with a ClassLoader that will resolve any R classes to Object.
+ // This is fine in this case as resource classes shouldn't be in the class
+ // hierarchy of any Fragments.
+ ClassLoader resourceStubbingClassLoader = new ClassLoader() {
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ if (name.matches(".*\\.R(\\$.+)?")) {
+ return Object.class;
+ }
+ return super.findClass(name);
+ }
+ };
+
+ // This doesn't use Class#isAssignableFrom to avoid us needing to load
+ // AndroidX's Fragment class, which may not be on the classpath.
+ try {
+ String binaryName = Type.getObjectType(internalType).getClassName();
+ Class<?> clazz = resourceStubbingClassLoader.loadClass(binaryName);
+ while (clazz != null) {
+ if (clazz.getName().equals("androidx.fragment.app.Fragment")) {
+ return true;
+ }
+ clazz = clazz.getSuperclass();
+ }
+ return false;
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ };
+ }
+ }
+
+ /**
+ * Updates the implementation of Fragment.getActivity() and Fragment.requireActivity().
+ */
+ private static class FragmentClassVisitor extends ClassVisitor {
+ private FragmentClassVisitor(ClassVisitor baseVisitor) {
+ super(Opcodes.ASM7, baseVisitor);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(
+ int access, String name, String descriptor, String signature, String[] exceptions) {
+ // Update the descriptor of getActivity() and requireActivity().
+ MethodVisitor baseVisitor;
+ if (descriptor.equals(OLD_METHOD_DESCRIPTOR)
+ && (name.equals(GET_ACTIVITY_METHOD_NAME)
+ || name.equals(REQUIRE_ACTIVITY_METHOD_NAME))) {
+ // Some Fragments in a Clank library implement an interface that defines an
+ // `Activity getActivity()` method. Fragment.getActivity() is considered its
+ // implementation from a typechecking perspective, but javac still generates a
+ // getActivity() method in these Fragments that call Fragment.getActivity(). This
+ // isn't an issue when the methods return different types, but after changing
+ // Fragment.getActivity() to return an Activity, this generated implementation is
+ // now overriding Fragment's, which it can't do because Fragment.getActivity() is
+ // final. We make it non-final here to avoid this issue.
+ baseVisitor = super.visitMethod(
+ access & ~Opcodes.ACC_FINAL, name, NEW_METHOD_DESCRIPTOR, null, exceptions);
+ } else {
+ baseVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
+ }
+
+ // Replace getActivity() with `return ContextUtils.activityFromContext(getContext());`
+ if (name.equals(GET_ACTIVITY_METHOD_NAME) && descriptor.equals(OLD_METHOD_DESCRIPTOR)) {
+ baseVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+ baseVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "androidx/fragment/app/Fragment",
+ "getContext", "()Landroid/content/Context;", false);
+ baseVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "org/chromium/utils/ContextUtils",
+ "activityFromContext", "(Landroid/content/Context;)Landroid/app/Activity;",
+ false);
+ baseVisitor.visitInsn(Opcodes.ARETURN);
+ // Since we set COMPUTE_FRAMES, the arguments of visitMaxs are ignored, but calling
+ // it forces ClassWriter to actually recompute the correct stack/local values.
+ // Without this call ClassWriter keeps the original stack=0,locals=1 which is wrong.
+ baseVisitor.visitMaxs(0, 0);
+ return null;
+ }
+
+ return new MethodRemapper(baseVisitor, new Remapper() {
+ @Override
+ public String mapType(String internalName) {
+ if (internalName.equals("androidx/fragment/app/FragmentActivity")) {
+ return "android/app/Activity";
+ }
+ return internalName;
+ }
+ });
+ }
+ }
+
+ /**
+ * Update SupportLifecycleFragmentImpl.getLifecycleActivity().
+ */
+ private static class SupportLifecycleFragmentImplClassVisitor extends ClassVisitor {
+ private SupportLifecycleFragmentImplClassVisitor(ClassVisitor baseVisitor) {
+ super(Opcodes.ASM7, baseVisitor);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(
+ int access, String name, String descriptor, String signature, String[] exceptions) {
+ // SupportLifecycleFragmentImpl has two getActivity methods:
+ // 1. public FragmentActivity getLifecycleActivity():
+ // This is what you'll see in the source. This delegates to Fragment.getActivity().
+ // 2. public Activity getLifecycleActivity():
+ // This is generated because the class implements LifecycleFragment, which
+ // declares this method, and delegates to #1.
+ //
+ // Here we change the return type of #1 and delete #2.
+ if (name.equals(GET_LIFECYCLE_ACTIVITY_METHOD_NAME)) {
+ if (descriptor.equals(OLD_METHOD_DESCRIPTOR)) {
+ return super.visitMethod(
+ access, name, NEW_METHOD_DESCRIPTOR, signature, exceptions);
+ }
+ return null;
+ }
+ return super.visitMethod(access, name, descriptor, signature, exceptions);
+ }
+ }
+}
diff --git a/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/MethodCheckerClassAdapter.java b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/MethodCheckerClassAdapter.java
new file mode 100644
index 0000000000..5aef275319
--- /dev/null
+++ b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/MethodCheckerClassAdapter.java
@@ -0,0 +1,136 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// 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 {@link android.view.View}.
+ * 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 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 || !isClassView(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 isClassView(String desc) {
+ Class currentClass = getClass(desc);
+ Class viewClass = getClass(VIEW_CLASS_DESCRIPTOR);
+ if (currentClass != null && viewClass != null) {
+ return viewClass.isAssignableFrom(currentClass);
+ }
+ return false;
+ }
+
+ 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");
+ }
+}
diff --git a/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/MethodDescription.java b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/MethodDescription.java
new file mode 100644
index 0000000000..23b14536e1
--- /dev/null
+++ b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/MethodDescription.java
@@ -0,0 +1,20 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.bytecode;
+
+class MethodDescription {
+ public final String methodName;
+ public final String description;
+ public final int access;
+ public Boolean shouldCreateOverride;
+
+ public MethodDescription(String methodName, String description, int access) {
+ this.methodName = methodName;
+ this.description = description;
+ this.access = access;
+ // A null value means we haven't checked the method.
+ this.shouldCreateOverride = null;
+ }
+}
diff --git a/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ParentMethodCheckerClassAdapter.java b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ParentMethodCheckerClassAdapter.java
new file mode 100644
index 0000000000..d913f1a73e
--- /dev/null
+++ b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ParentMethodCheckerClassAdapter.java
@@ -0,0 +1,94 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// 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.Opcodes.ACC_FINAL;
+import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
+import static org.objectweb.asm.Opcodes.ASM7;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+
+import java.util.ArrayList;
+
+/**
+ * This ClassVisitor checks if the given class overrides methods on {@code methodsToCheck}, and if
+ * so it determines whether they can be overridden by a child class. If at the end any unchecked
+ * methods remain then we recurse on the class's superclass.
+ */
+class ParentMethodCheckerClassAdapter extends ClassVisitor {
+ private static final String OBJECT_CLASS_DESCRIPTOR = "java.lang.Object";
+
+ private final ArrayList<MethodDescription> mMethodsToCheck;
+ private final ClassLoader mJarClassLoader;
+ private String mSuperName;
+ private boolean mIsCheckingObjectClass;
+
+ public ParentMethodCheckerClassAdapter(
+ 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);
+
+ if (name.equals(OBJECT_CLASS_DESCRIPTOR)) {
+ mIsCheckingObjectClass = true;
+ return;
+ }
+
+ mSuperName = superName;
+ }
+
+ @Override
+ public MethodVisitor visitMethod(
+ int access, String name, String descriptor, String signature, String[] exceptions) {
+ if (mIsCheckingObjectClass) {
+ return super.visitMethod(access, name, descriptor, signature, exceptions);
+ }
+
+ for (MethodDescription methodToCheck : mMethodsToCheck) {
+ if (methodToCheck.shouldCreateOverride != null || !methodToCheck.methodName.equals(name)
+ || !methodToCheck.description.equals(descriptor)) {
+ continue;
+ }
+
+ // This class contains methodToCheck.
+ boolean isMethodPrivate = (access & ACC_PRIVATE) == ACC_PRIVATE;
+ boolean isMethodFinal = (access & ACC_FINAL) == ACC_FINAL;
+ // If the method is private or final then don't create an override.
+ methodToCheck.shouldCreateOverride = !isMethodPrivate && !isMethodFinal;
+ }
+
+ return super.visitMethod(access, name, descriptor, signature, exceptions);
+ }
+
+ @Override
+ public void visitEnd() {
+ if (mIsCheckingObjectClass) {
+ return;
+ }
+
+ boolean areAnyUncheckedMethods = false;
+
+ for (MethodDescription method : mMethodsToCheck) {
+ if (method.shouldCreateOverride == null) {
+ areAnyUncheckedMethods = true;
+ break;
+ }
+ }
+
+ if (areAnyUncheckedMethods) {
+ MethodCheckerClassAdapter.checkParentClass(
+ mSuperName, mMethodsToCheck, mJarClassLoader);
+ }
+
+ super.visitEnd();
+ }
+}
diff --git a/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TraceEventAdder.java b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TraceEventAdder.java
new file mode 100644
index 0000000000..51f323f00a
--- /dev/null
+++ b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TraceEventAdder.java
@@ -0,0 +1,87 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// 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/bytecode_rewriter.py
+ if (args.length < 2) {
+ System.err.println(
+ "Expected arguments: <input.jar> <output.jar> <input classpath jars>");
+ System.exit(1);
+ }
+
+ String input = args[0];
+ String output = args[1];
+
+ ArrayList<String> classPathJarsPaths = new ArrayList<>();
+ classPathJarsPaths.add(input);
+ classPathJarsPaths.addAll(Arrays.asList(Arrays.copyOfRange(args, 2, args.length)));
+ ClassLoader classPathJarsClassLoader = ByteCodeProcessor.loadJars(classPathJarsPaths);
+
+ TraceEventAdder adder = new TraceEventAdder(classPathJarsClassLoader);
+ adder.rewrite(new File(input), new File(output));
+ }
+
+ public TraceEventAdder(ClassLoader classPathJarsClassLoader) {
+ mClassPathJarsClassLoader = classPathJarsClassLoader;
+ }
+
+ @Override
+ protected boolean shouldRewriteClass(String classPath) {
+ try {
+ // If this jar's dependencies can't find Chromium's TraceEvent class then skip this
+ // class. Conceptually this could be fixed by adding a dependency on //base:base_java
+ // but that would cause circular dependencies and any changes to base_java would cause
+ // all android_library targets to require rebuilding.
+ mClassPathJarsClassLoader.loadClass("org.chromium.base.TraceEvent");
+ return true;
+ } catch (ClassNotFoundException ex) {
+ return false;
+ }
+ }
+
+ @Override
+ protected boolean shouldRewriteClass(ClassReader classReader) {
+ mMethodsToTrace = new ArrayList<>(Arrays.asList(
+ 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)));
+
+ // 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;
+ }
+}
diff --git a/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TraceEventAdderClassAdapter.java b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TraceEventAdderClassAdapter.java
new file mode 100644
index 0000000000..c4a152d995
--- /dev/null
+++ b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TraceEventAdderClassAdapter.java
@@ -0,0 +1,47 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// 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.Opcodes.ASM7;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+
+import java.util.ArrayList;
+
+/**
+ * A ClassVisitor for adding TraceEvent.begin and TraceEvent.end methods to any methods specified in
+ * a list.
+ */
+class TraceEventAdderClassAdapter extends ClassVisitor {
+ private final ArrayList<MethodDescription> mMethodsToTrace;
+ private String mShortClassName;
+
+ TraceEventAdderClassAdapter(ClassVisitor visitor, ArrayList<MethodDescription> methodsToTrace) {
+ super(ASM7, visitor);
+ mMethodsToTrace = methodsToTrace;
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName,
+ String[] interfaces) {
+ super.visit(version, access, name, signature, superName, interfaces);
+ mShortClassName = name.substring(name.lastIndexOf('/') + 1);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(final int access, final String name, String desc,
+ String signature, String[] exceptions) {
+ MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
+
+ for (MethodDescription method : mMethodsToTrace) {
+ if (method.methodName.equals(name) && method.description.equals(desc)) {
+ return new TraceEventAdderMethodAdapter(mv, mShortClassName, name);
+ }
+ }
+
+ return mv;
+ }
+}
diff --git a/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TraceEventAdderMethodAdapter.java b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TraceEventAdderMethodAdapter.java
new file mode 100644
index 0000000000..042b3d3c5f
--- /dev/null
+++ b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TraceEventAdderMethodAdapter.java
@@ -0,0 +1,83 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// 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.Opcodes.ASM7;
+import static org.objectweb.asm.Opcodes.ATHROW;
+import static org.objectweb.asm.Opcodes.INVOKESTATIC;
+import static org.objectweb.asm.Opcodes.IRETURN;
+import static org.objectweb.asm.Opcodes.RETURN;
+
+import static org.chromium.bytecode.TypeUtils.STRING;
+import static org.chromium.bytecode.TypeUtils.VOID;
+
+import org.objectweb.asm.MethodVisitor;
+
+/**
+ * MethodVisitor that wraps all code in TraceEvent.begin and TraceEvent.end calls. TraceEvent.end
+ * calls are added on all returns and thrown exceptions.
+ *
+ * Example:
+ * <pre>
+ * {@code
+ * int methodToTrace(String foo){
+ *
+ * //Line added by rewriter:
+ * TraceEvent.begin("ClassName.methodToTrace");
+ *
+ * if(foo == null){
+ * //Line added by rewriter:
+ * TraceEvent.end("ClassName.methodToTrace");
+ *
+ * throw new Exception();
+ * }
+ * else if(foo.equals("Two")){
+ * //Line added by rewriter:
+ * TraceEvent.end("ClassName.methodToTrace");
+ *
+ * return 2;
+ * }
+ *
+ * //Line added by rewriter:
+ * TraceEvent.end("ClassName.methodToTrace");
+ *
+ * return 0;
+ * }
+ * }
+ * </pre>
+ *
+ */
+class TraceEventAdderMethodAdapter extends MethodVisitor {
+ private static final String TRACE_EVENT_DESCRIPTOR = "org/chromium/base/TraceEvent";
+ private static final String TRACE_EVENT_SIGNATURE = TypeUtils.getMethodDescriptor(VOID, STRING);
+ private final String mEventName;
+
+ public TraceEventAdderMethodAdapter(
+ MethodVisitor methodVisitor, String shortClassName, String methodName) {
+ super(ASM7, methodVisitor);
+
+ mEventName = shortClassName + "." + methodName;
+ }
+
+ @Override
+ public void visitCode() {
+ super.visitCode();
+
+ mv.visitLdcInsn(mEventName);
+ mv.visitMethodInsn(
+ INVOKESTATIC, TRACE_EVENT_DESCRIPTOR, "begin", TRACE_EVENT_SIGNATURE, false);
+ }
+
+ @Override
+ public void visitInsn(int opcode) {
+ if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {
+ mv.visitLdcInsn(mEventName);
+ mv.visitMethodInsn(
+ INVOKESTATIC, TRACE_EVENT_DESCRIPTOR, "end", TRACE_EVENT_SIGNATURE, false);
+ }
+
+ mv.visitInsn(opcode);
+ }
+}
diff --git a/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TypeUtils.java b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TypeUtils.java
new file mode 100644
index 0000000000..ed2dc2dc24
--- /dev/null
+++ b/third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TypeUtils.java
@@ -0,0 +1,87 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// 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.Type;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Utility methods for accessing {@link Type}s Strings.
+ *
+ * Useful definitions to keep in mind when using this class:
+ * Internal name - The fully qualified name for a type with dots replaced by slashes. Not really
+ * relevant for primitive types.
+ * Type descriptor - Single letters for primitive types, "L" + internal name + ";" for class types.
+ *
+ * The methods in this class accept internal names or primitive type descriptors.
+ */
+class TypeUtils {
+ static final String ASSERTION_ERROR = "java/lang/AssertionError";
+ static final String ASSET_MANAGER = "android/content/res/AssetManager";
+ static final String BUILD_HOOKS = "org/chromium/build/BuildHooks";
+ static final String BUILD_HOOKS_ANDROID = "org/chromium/build/BuildHooksAndroid";
+ static final String CONFIGURATION = "android/content/res/Configuration";
+ static final String CONTEXT = "android/content/Context";
+ static final String CONTEXT_WRAPPER = "android/content/ContextWrapper";
+ static final String RESOURCES = "android/content/res/Resources";
+ static final String STRING = "java/lang/String";
+ static final String THEME = "android/content/res/Resources$Theme";
+
+ static final String BOOLEAN = "Z";
+ static final String INT = "I";
+ static final String VOID = "V";
+ private static final Map<String, Type> PRIMITIVE_DESCRIPTORS;
+ static {
+ PRIMITIVE_DESCRIPTORS = new HashMap<>();
+ PRIMITIVE_DESCRIPTORS.put(Type.BOOLEAN_TYPE.toString(), Type.BOOLEAN_TYPE);
+ PRIMITIVE_DESCRIPTORS.put(Type.INT_TYPE.toString(), Type.INT_TYPE);
+ PRIMITIVE_DESCRIPTORS.put(Type.VOID_TYPE.toString(), Type.VOID_TYPE);
+ }
+
+ /**
+ * Returns the full method signature with internal names.
+ *
+ * @param methodName Name of the method (ex. "getResources").
+ * @param returnType Internal name for the return type.
+ * @param argumentTypes List of internal names for argument types.
+ * @return String representation of the method signature.
+ */
+ static String getMethodSignature(
+ String methodName, String returnType, String... argumentTypes) {
+ return methodName + getMethodDescriptor(returnType, argumentTypes);
+ }
+
+ /**
+ * Builds a method descriptor suitable for use with {@link org.objectweb.asm.MethodVisitor}.
+ *
+ * @param returnType Internal name for the return type of the method (primitive or class).
+ * @param argumentTypes Internal names for the argument types (primitive or class).
+ * @return The generated method descriptor.
+ */
+ static String getMethodDescriptor(String returnType, String... argumentTypes) {
+ Type[] typedArguments = new Type[argumentTypes.length];
+ for (int i = 0; i < argumentTypes.length; ++i) {
+ // Argument list should be empty in this case, not V (void).
+ assert !Type.VOID_TYPE.toString().equals(argumentTypes[i]);
+ typedArguments[i] = convert(argumentTypes[i]);
+ }
+ return Type.getMethodDescriptor(convert(returnType), typedArguments);
+ }
+
+ /**
+ * Converts an internal name for a type to a {@link Type}.
+ *
+ * @param type Internal name for a type (primitive or class).
+ * @return The resulting Type.
+ */
+ private static Type convert(String type) {
+ if (PRIMITIVE_DESCRIPTORS.containsKey(type)) {
+ return PRIMITIVE_DESCRIPTORS.get(type);
+ }
+ return Type.getObjectType(type);
+ }
+}