From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../libwebrtc/build/android/bytecode/BUILD.gn | 86 ++++++++ .../org/chromium/bytecode/ByteCodeProcessor.java | 167 +++++++++++++++ .../org/chromium/bytecode/ByteCodeRewriter.java | 101 +++++++++ .../org/chromium/bytecode/ClassPathValidator.java | 233 ++++++++++++++++++++ .../EmptyOverrideGeneratorClassAdapter.java | 103 +++++++++ .../bytecode/FragmentActivityReplacer.java | 238 +++++++++++++++++++++ .../bytecode/MethodCheckerClassAdapter.java | 136 ++++++++++++ .../org/chromium/bytecode/MethodDescription.java | 20 ++ .../bytecode/ParentMethodCheckerClassAdapter.java | 94 ++++++++ .../org/chromium/bytecode/TraceEventAdder.java | 87 ++++++++ .../bytecode/TraceEventAdderClassAdapter.java | 47 ++++ .../bytecode/TraceEventAdderMethodAdapter.java | 83 +++++++ .../java/org/chromium/bytecode/TypeUtils.java | 87 ++++++++ 13 files changed, 1482 insertions(+) create mode 100644 third_party/libwebrtc/build/android/bytecode/BUILD.gn create mode 100644 third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ByteCodeProcessor.java create mode 100644 third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ByteCodeRewriter.java create mode 100644 third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ClassPathValidator.java create mode 100644 third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/EmptyOverrideGeneratorClassAdapter.java create mode 100644 third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/FragmentActivityReplacer.java create mode 100644 third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/MethodCheckerClassAdapter.java create mode 100644 third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/MethodDescription.java create mode 100644 third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/ParentMethodCheckerClassAdapter.java create mode 100644 third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TraceEventAdder.java create mode 100644 third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TraceEventAdderClassAdapter.java create mode 100644 third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TraceEventAdderMethodAdapter.java create mode 100644 third_party/libwebrtc/build/android/bytecode/java/org/chromium/bytecode/TypeUtils.java (limited to 'third_party/libwebrtc/build/android/bytecode') 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 sFullClassPathJarPaths; + private static Set sMissingClassesAllowlist; + private static Map 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 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 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 sdkJarPaths = new ArrayList<>(); + currIndex = parseListArgument(args, currIndex, sdkJarPaths); + + ArrayList directClassPathJarPaths = new ArrayList<>(); + directClassPathJarPaths.add(inputJarPath); + directClassPathJarPaths.addAll(sdkJarPaths); + currIndex = parseListArgument(args, currIndex, directClassPathJarPaths); + sDirectClassPathClassLoader = loadJars(directClassPathJarPaths); + + ArrayList fullClassPathJarPaths = new ArrayList<>(); + currIndex = parseListArgument(args, currIndex, fullClassPathJarPaths); + ArrayList 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>> mDirectErrors = + Collections.synchronizedMap(new TreeMap<>()); + // Missing classes we only track the first one for each jar. + // Map of missingClass -> srcClass. + private final Map 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 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 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 jarsOnlyInFullClassPath, + Set 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> 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 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> 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> entry : missingClasses.entrySet()) { + String missingClass = entry.getKey(); + Set 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 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 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>> 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 mMethodsToGenerate; + private String mSuperClassName; + private boolean mIsAbstract; + private boolean mIsInterface; + + public EmptyOverrideGeneratorClassAdapter( + ClassVisitor cv, ArrayList 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. + *
+     * {@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);
+     * }
+     * }
+     * 
+ * + * 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] "); + 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 mMethodsToCheck; + private final ClassLoader mJarClassLoader; + private String mSuperName; + + public MethodCheckerClassAdapter( + ArrayList 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 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 mMethodsToCheck; + private final ClassLoader mJarClassLoader; + private String mSuperName; + private boolean mIsCheckingObjectClass; + + public ParentMethodCheckerClassAdapter( + ArrayList 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 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: "); + System.exit(1); + } + + String input = args[0]; + String output = args[1]; + + ArrayList 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 mMethodsToTrace; + private String mShortClassName; + + TraceEventAdderClassAdapter(ClassVisitor visitor, ArrayList 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: + *
+ *   {@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;
+ *      }
+ *   }
+ * 
+ * + */ +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 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); + } +} -- cgit v1.2.3