diff options
Diffstat (limited to 'deps/v8/build/android/bytecode/java/org')
8 files changed, 1241 insertions, 0 deletions
diff --git a/deps/v8/build/android/bytecode/java/org/chromium/bytecode/AssertionEnablerClassAdapter.java b/deps/v8/build/android/bytecode/java/org/chromium/bytecode/AssertionEnablerClassAdapter.java new file mode 100644 index 0000000000..0a903a60f9 --- /dev/null +++ b/deps/v8/build/android/bytecode/java/org/chromium/bytecode/AssertionEnablerClassAdapter.java @@ -0,0 +1,109 @@ +// 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 static org.chromium.bytecode.TypeUtils.ASSERTION_ERROR; +import static org.chromium.bytecode.TypeUtils.BUILD_HOOKS; +import static org.chromium.bytecode.TypeUtils.VOID; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +/** + * An ClassVisitor for replacing Java ASSERT statements with a function by modifying Java bytecode. + * + * We do this in two steps, first step is to enable assert. + * Following bytecode is generated for each class with ASSERT statements: + * 0: ldc #8 // class CLASSNAME + * 2: invokevirtual #9 // Method java/lang/Class.desiredAssertionStatus:()Z + * 5: ifne 12 + * 8: iconst_1 + * 9: goto 13 + * 12: iconst_0 + * 13: putstatic #2 // Field $assertionsDisabled:Z + * Replaces line #13 to the following: + * 13: pop + * Consequently, $assertionsDisabled is assigned the default value FALSE. + * This is done in the first if statement in overridden visitFieldInsn. We do this per per-assert. + * + * Second step is to replace assert statement with a function: + * The followed instructions are generated by a java assert statement: + * getstatic #3 // Field $assertionsDisabled:Z + * ifne 118 // Jump to instruction as if assertion if not enabled + * ... + * ifne 19 + * new #4 // class java/lang/AssertionError + * dup + * ldc #5 // String (don't have this line if no assert message given) + * invokespecial #6 // Method java/lang/AssertionError. + * athrow + * Replace athrow with: + * invokestatic #7 // Method org/chromium/base/JavaExceptionReporter.assertFailureHandler + * goto 118 + * JavaExceptionReporter.assertFailureHandler is a function that handles the AssertionError, + * 118 is the instruction to execute as if assertion if not enabled. + */ +class AssertionEnablerClassAdapter extends ClassVisitor { + AssertionEnablerClassAdapter(ClassVisitor visitor) { + super(Opcodes.ASM5, visitor); + } + + @Override + public MethodVisitor visitMethod(final int access, final String name, String desc, + String signature, String[] exceptions) { + return new RewriteAssertMethodVisitor( + Opcodes.ASM5, super.visitMethod(access, name, desc, signature, exceptions)); + } + + static class RewriteAssertMethodVisitor extends MethodVisitor { + static final String ASSERTION_DISABLED_NAME = "$assertionsDisabled"; + static final String INSERT_INSTRUCTION_NAME = "assertFailureHandler"; + static final String INSERT_INSTRUCTION_DESC = + TypeUtils.getMethodDescriptor(VOID, ASSERTION_ERROR); + static final boolean INSERT_INSTRUCTION_ITF = false; + + boolean mStartLoadingAssert; + Label mGotoLabel; + + public RewriteAssertMethodVisitor(int api, MethodVisitor mv) { + super(api, mv); + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + if (opcode == Opcodes.PUTSTATIC && name.equals(ASSERTION_DISABLED_NAME)) { + super.visitInsn(Opcodes.POP); // enable assert + } else if (opcode == Opcodes.GETSTATIC && name.equals(ASSERTION_DISABLED_NAME)) { + mStartLoadingAssert = true; + super.visitFieldInsn(opcode, owner, name, desc); + } else { + super.visitFieldInsn(opcode, owner, name, desc); + } + } + + @Override + public void visitJumpInsn(int opcode, Label label) { + if (mStartLoadingAssert && opcode == Opcodes.IFNE && mGotoLabel == null) { + mGotoLabel = label; + } + super.visitJumpInsn(opcode, label); + } + + @Override + public void visitInsn(int opcode) { + if (!mStartLoadingAssert || opcode != Opcodes.ATHROW) { + super.visitInsn(opcode); + } else { + super.visitMethodInsn(Opcodes.INVOKESTATIC, BUILD_HOOKS, INSERT_INSTRUCTION_NAME, + INSERT_INSTRUCTION_DESC, INSERT_INSTRUCTION_ITF); + super.visitJumpInsn(Opcodes.GOTO, mGotoLabel); + mStartLoadingAssert = false; + mGotoLabel = null; + } + } + } +}
\ No newline at end of file diff --git a/deps/v8/build/android/bytecode/java/org/chromium/bytecode/ByteCodeProcessor.java b/deps/v8/build/android/bytecode/java/org/chromium/bytecode/ByteCodeProcessor.java new file mode 100644 index 0000000000..37dc192d81 --- /dev/null +++ b/deps/v8/build/android/bytecode/java/org/chromium/bytecode/ByteCodeProcessor.java @@ -0,0 +1,293 @@ +// 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 static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +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.Future; +import java.util.zip.CRC32; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +/** + * Java application that takes in an input jar, performs a series of bytecode transformations, + * and generates an output jar. + * + * Two types of transformations are performed: + * 1) Enabling assertions via {@link AssertionEnablerClassAdapter} + * 2) Providing support for custom resources via {@link CustomResourcesClassAdapter} + */ +class ByteCodeProcessor { + private static final String CLASS_FILE_SUFFIX = ".class"; + private static final String TEMPORARY_FILE_SUFFIX = ".temp"; + private static final int BUFFER_SIZE = 16384; + private static boolean sVerbose; + private static boolean sIsPrebuilt; + private static boolean sShouldAssert; + private static boolean sShouldUseCustomResources; + private static boolean sShouldUseThreadAnnotations; + private static boolean sShouldCheckClassPath; + private static ClassLoader sDirectClassPathClassLoader; + private static ClassLoader sFullClassPathClassLoader; + private static Set<String> sFullClassPathJarPaths; + private static Set<String> sSplitCompatClassNames; + private static ClassPathValidator sValidator; + + private static class EntryDataPair { + private final ZipEntry mEntry; + private final byte[] mData; + + private EntryDataPair(ZipEntry mEntry, byte[] mData) { + this.mEntry = mEntry; + this.mData = mData; + } + + private static EntryDataPair create(String zipPath, byte[] data) { + ZipEntry entry = new ZipEntry(zipPath); + entry.setMethod(ZipEntry.STORED); + entry.setTime(0); + entry.setSize(data.length); + CRC32 crc = new CRC32(); + crc.update(data); + entry.setCrc(crc.getValue()); + return new EntryDataPair(entry, data); + } + } + + private static EntryDataPair processEntry(ZipEntry entry, byte[] data) + throws ClassPathValidator.ClassNotLoadedException { + // Copy all non-.class files to the output jar. + if (entry.isDirectory() || !entry.getName().endsWith(CLASS_FILE_SUFFIX)) { + return new EntryDataPair(entry, data); + } + + ClassReader reader = new ClassReader(data); + + if (sShouldCheckClassPath) { + sValidator.validateClassPathsAndOutput(reader, sDirectClassPathClassLoader, + sFullClassPathClassLoader, sFullClassPathJarPaths, sIsPrebuilt, sVerbose); + } + + ClassWriter writer; + if (sShouldUseCustomResources) { + // Use the COMPUTE_FRAMES flag to have asm figure out the stack map frames. + // This is necessary because GCMBaseIntentService in android_gcm_java contains + // incorrect stack map frames. This option slows down processing time by 2x. + writer = new CustomClassLoaderClassWriter( + sFullClassPathClassLoader, reader, COMPUTE_FRAMES); + } else { + writer = new ClassWriter(reader, 0); + } + ClassVisitor chain = writer; + /* DEBUGGING: + To see the bytecode for a specific class: + if (entry.getName().contains("YourClassName")) { + chain = new TraceClassVisitor(chain, new PrintWriter(System.out)); + } + To see objectweb.asm code that will generate bytecode for a given class: + java -cp "third_party/ow2_asm/lib/asm-5.0.1.jar:third_party/ow2_asm/lib/"\ + "asm-util-5.0.1.jar:out/Debug/lib.java/jar_containing_yourclass.jar" \ + org.objectweb.asm.util.ASMifier org.package.YourClassName + */ + if (sShouldUseThreadAnnotations) { + chain = new ThreadAssertionClassAdapter(chain); + } + if (sShouldAssert) { + chain = new AssertionEnablerClassAdapter(chain); + } + if (sShouldUseCustomResources) { + chain = new CustomResourcesClassAdapter( + chain, reader.getClassName(), reader.getSuperName(), sFullClassPathClassLoader); + } + if (!sSplitCompatClassNames.isEmpty()) { + chain = new SplitCompatClassAdapter( + chain, sSplitCompatClassNames, sFullClassPathClassLoader); + } + reader.accept(chain, 0); + byte[] patchedByteCode = writer.toByteArray(); + return EntryDataPair.create(entry.getName(), patchedByteCode); + } + + private static void process(String inputJarPath, String outputJarPath) + throws ClassPathValidator.ClassNotLoadedException, ExecutionException, + InterruptedException { + String tempJarPath = outputJarPath + TEMPORARY_FILE_SUFFIX; + ExecutorService executorService = + Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + try (ZipInputStream inputStream = new ZipInputStream( + new BufferedInputStream(new FileInputStream(inputJarPath))); + ZipOutputStream tempStream = new ZipOutputStream( + new BufferedOutputStream(new FileOutputStream(tempJarPath)))) { + List<Future<EntryDataPair>> list = new ArrayList<>(); + while (true) { + ZipEntry entry = inputStream.getNextEntry(); + if (entry == null) { + break; + } + byte[] data = readAllBytes(inputStream); + list.add(executorService.submit(() -> processEntry(entry, data))); + } + executorService.shutdown(); // This is essential in order to avoid waiting infinitely. + // Write the zip file entries in order to preserve determinism. + for (Future<EntryDataPair> futurePair : list) { + EntryDataPair pair = futurePair.get(); + tempStream.putNextEntry(pair.mEntry); + tempStream.write(pair.mData); + tempStream.closeEntry(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + try { + Path src = Paths.get(tempJarPath); + Path dest = Paths.get(outputJarPath); + Files.move(src, dest, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException ioException) { + throw new RuntimeException(ioException); + } + + if (sValidator.hasErrors()) { + System.err.println("Direct classpath is incomplete. To fix, add deps on the " + + "GN target(s) that provide:"); + for (Map.Entry<String, Map<String, Set<String>>> entry : + sValidator.getErrors().entrySet()) { + printValidationError(System.err, entry.getKey(), entry.getValue()); + } + System.exit(1); + } + } + + private static void printValidationError( + PrintStream out, String jarName, Map<String, Set<String>> missingClasses) { + out.print(" * "); + out.println(jarName); + int i = 0; + final int numErrorsPerJar = 2; + // 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 == numErrorsPerJar) { + out.print(String.format("And %d more...", missingClasses.size() - numErrorsPerJar)); + 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++; + } + } + + 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); + } + + 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 inputJarPath = args[currIndex++]; + String outputJarPath = args[currIndex++]; + sVerbose = args[currIndex++].equals("--verbose"); + sIsPrebuilt = args[currIndex++].equals("--is-prebuilt"); + sShouldAssert = args[currIndex++].equals("--enable-assert"); + sShouldUseCustomResources = args[currIndex++].equals("--enable-custom-resources"); + sShouldUseThreadAnnotations = args[currIndex++].equals("--enable-thread-annotations"); + sShouldCheckClassPath = args[currIndex++].equals("--enable-check-class-path"); + int sdkJarsLength = Integer.parseInt(args[currIndex++]); + List<String> sdkJarPaths = + Arrays.asList(Arrays.copyOfRange(args, currIndex, currIndex + sdkJarsLength)); + currIndex += sdkJarsLength; + + int directJarsLength = Integer.parseInt(args[currIndex++]); + ArrayList<String> directClassPathJarPaths = new ArrayList<>(); + directClassPathJarPaths.add(inputJarPath); + directClassPathJarPaths.addAll(sdkJarPaths); + directClassPathJarPaths.addAll( + Arrays.asList(Arrays.copyOfRange(args, currIndex, currIndex + directJarsLength))); + currIndex += directJarsLength; + sDirectClassPathClassLoader = loadJars(directClassPathJarPaths); + + // Load list of class names that need to be fixed. + int splitCompatClassNamesLength = Integer.parseInt(args[currIndex++]); + sSplitCompatClassNames = new HashSet<>(); + sSplitCompatClassNames.addAll(Arrays.asList( + Arrays.copyOfRange(args, currIndex, currIndex + splitCompatClassNamesLength))); + currIndex += splitCompatClassNamesLength; + + // Load all jars that are on the classpath for the input jar for analyzing class hierarchy. + sFullClassPathJarPaths = new HashSet<>(); + sFullClassPathJarPaths.clear(); + sFullClassPathJarPaths.add(inputJarPath); + sFullClassPathJarPaths.addAll(sdkJarPaths); + sFullClassPathJarPaths.addAll( + Arrays.asList(Arrays.copyOfRange(args, currIndex, args.length))); + sFullClassPathClassLoader = loadJars(sFullClassPathJarPaths); + sFullClassPathJarPaths.removeAll(directClassPathJarPaths); + + sValidator = new ClassPathValidator(); + process(inputJarPath, outputJarPath); + } +} diff --git a/deps/v8/build/android/bytecode/java/org/chromium/bytecode/ClassPathValidator.java b/deps/v8/build/android/bytecode/java/org/chromium/bytecode/ClassPathValidator.java new file mode 100644 index 0000000000..c35c3f6820 --- /dev/null +++ b/deps/v8/build/android/bytecode/java/org/chromium/bytecode/ClassPathValidator.java @@ -0,0 +1,167 @@ +// 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.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; + +/** + * 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 { + // 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>>> mErrors = 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 printAndQuit(ClassNotLoadedException e, ClassReader classReader, + boolean verbose) throws ClassNotLoadedException { + System.err.println("Class \"" + e.getClassName() + + "\" not found on any classpath. Used by class \"" + classReader.getClassName() + + "\""); + if (verbose) { + throw e; + } + System.exit(1); + } + + 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. + * @throws ClassNotLoadedException thrown if it can't load a certain class. + */ + private static void validateClassPath(ClassReader classReader, ClassLoader classLoader) + throws ClassNotLoadedException { + 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) { + validateClass(classLoader, classReader.readUTF8(offset, charBuffer)); + } + } + } + + public void validateClassPathsAndOutput(ClassReader classReader, + ClassLoader directClassPathClassLoader, ClassLoader fullClassPathClassLoader, + Collection<String> jarsOnlyInFullClassPath, boolean isPrebuilt, boolean verbose) + throws ClassNotLoadedException { + if (isPrebuilt) { + // Prebuilts only need transitive dependencies checked, not direct dependencies. + try { + validateClassPath(classReader, fullClassPathClassLoader); + } catch (ClassNotLoadedException e) { + printAndQuit(e, classReader, verbose); + } + } else { + try { + validateClassPath(classReader, directClassPathClassLoader); + } catch (ClassNotLoadedException e) { + try { + validateClass(fullClassPathClassLoader, e.getClassName()); + } catch (ClassNotLoadedException d) { + printAndQuit(d, classReader, verbose); + } + 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()); + Map<String, Set<String>> failedClassesByMissingClass = mErrors.get(jarPath); + if (failedClassesByMissingClass == null) { + // TreeMap so that error messages have sorted list of classes. + failedClassesByMissingClass = new TreeMap<>(); + mErrors.put(jarPath, failedClassesByMissingClass); + } + Set<String> failedClasses = + failedClassesByMissingClass.get(e.getClassName()); + if (failedClasses == null) { + failedClasses = new TreeSet<>(); + failedClassesByMissingClass.put(e.getClassName(), failedClasses); + } + failedClasses.add(classReader.getClassName()); + break; + } catch (ClassNotLoadedException f) { + } + } + } + } + } + + public Map<String, Map<String, Set<String>>> getErrors() { + return mErrors; + } + + public boolean hasErrors() { + return !mErrors.isEmpty(); + } +} diff --git a/deps/v8/build/android/bytecode/java/org/chromium/bytecode/CustomClassLoaderClassWriter.java b/deps/v8/build/android/bytecode/java/org/chromium/bytecode/CustomClassLoaderClassWriter.java new file mode 100644 index 0000000000..3a52c85d56 --- /dev/null +++ b/deps/v8/build/android/bytecode/java/org/chromium/bytecode/CustomClassLoaderClassWriter.java @@ -0,0 +1,51 @@ +// 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 org.objectweb.asm.ClassWriter; + +/** + * A ClassWriter that uses a custom class loader. + */ +class CustomClassLoaderClassWriter extends ClassWriter { + private ClassLoader mClassLoader; + + public CustomClassLoaderClassWriter(ClassLoader classLoader, ClassReader reader, int flags) { + super(reader, flags); + this.mClassLoader = classLoader; + } + + /** + * The only modifications from the org.objectweb.asm.ClassWriter implementations is that this + * method is final and it uses a custom ClassLoader. + * + * See https://github.com/llbit/ow2-asm/blob/master/src/org/objectweb/asm/ClassWriter.java. + */ + @Override + protected final String getCommonSuperClass(final String type1, final String type2) { + Class<?> c, d; + try { + c = Class.forName(type1.replace('/', '.'), false, mClassLoader); + d = Class.forName(type2.replace('/', '.'), false, mClassLoader); + } catch (Exception e) { + throw new RuntimeException(e.toString()); + } + if (c.isAssignableFrom(d)) { + return type1; + } + if (d.isAssignableFrom(c)) { + return type2; + } + if (c.isInterface() || d.isInterface()) { + return "java/lang/Object"; + } else { + do { + c = c.getSuperclass(); + } while (!c.isAssignableFrom(d)); + return c.getName().replace('.', '/'); + } + } +} diff --git a/deps/v8/build/android/bytecode/java/org/chromium/bytecode/CustomResourcesClassAdapter.java b/deps/v8/build/android/bytecode/java/org/chromium/bytecode/CustomResourcesClassAdapter.java new file mode 100644 index 0000000000..96205b8815 --- /dev/null +++ b/deps/v8/build/android/bytecode/java/org/chromium/bytecode/CustomResourcesClassAdapter.java @@ -0,0 +1,302 @@ +// 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 static org.objectweb.asm.Opcodes.ACC_PUBLIC; +import static org.objectweb.asm.Opcodes.ACONST_NULL; +import static org.objectweb.asm.Opcodes.ALOAD; +import static org.objectweb.asm.Opcodes.ARETURN; +import static org.objectweb.asm.Opcodes.ASM5; +import static org.objectweb.asm.Opcodes.BIPUSH; +import static org.objectweb.asm.Opcodes.GETSTATIC; +import static org.objectweb.asm.Opcodes.IFNE; +import static org.objectweb.asm.Opcodes.IF_ICMPGE; +import static org.objectweb.asm.Opcodes.ILOAD; +import static org.objectweb.asm.Opcodes.INVOKESPECIAL; +import static org.objectweb.asm.Opcodes.INVOKESTATIC; +import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; +import static org.objectweb.asm.Opcodes.RETURN; + +import static org.chromium.bytecode.TypeUtils.ASSET_MANAGER; +import static org.chromium.bytecode.TypeUtils.BOOLEAN; +import static org.chromium.bytecode.TypeUtils.BUILD_HOOKS_ANDROID; +import static org.chromium.bytecode.TypeUtils.CONFIGURATION; +import static org.chromium.bytecode.TypeUtils.CONTEXT; +import static org.chromium.bytecode.TypeUtils.CONTEXT_WRAPPER; +import static org.chromium.bytecode.TypeUtils.INT; +import static org.chromium.bytecode.TypeUtils.RESOURCES; +import static org.chromium.bytecode.TypeUtils.STRING; +import static org.chromium.bytecode.TypeUtils.THEME; +import static org.chromium.bytecode.TypeUtils.VOID; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import java.util.Arrays; +import java.util.List; + +/** + * A ClassVisitor for providing access to custom resources via BuildHooksAndroid. + * + * The goal of this class is to provide hooks into all places where android resources + * are available so that they can be modified before use. This is done by rewriting the bytecode + * for all callable definitions of certain Context methods, specifically: + * - getResources + * - getAssets + * - getTheme + * - setTheme + * - createConfigurationContext + * + * Only classes at the framework boundary are rewritten since presumably all other indirect Context + * subclasses will end up calling their respective super methods (i.e. we bytecode rewrite + * BaseChromiumApplication since it extends Application, but not ContentApplication since it + * extends a non-framework subclass. + */ +class CustomResourcesClassAdapter extends ClassVisitor { + private static final String IS_ENABLED_METHOD = "isEnabled"; + private static final String IS_ENABLED_DESCRIPTOR = TypeUtils.getMethodDescriptor(BOOLEAN); + // Cached since this is used so often. + private static final String GET_IDENTIFIER_DESCRIPTOR = + TypeUtils.getMethodDescriptor(INT, STRING, STRING, STRING); + + // Existing methods are more difficult to handle, and not currently needed. + private static final List<String> PROHIBITED_METHODS = Arrays.asList( + TypeUtils.getMethodSignature("getResources", RESOURCES), + TypeUtils.getMethodSignature("getAssets", ASSET_MANAGER), + TypeUtils.getMethodSignature("getTheme", THEME), + TypeUtils.getMethodSignature("createConfigurationContext", CONTEXT, CONFIGURATION), + TypeUtils.getMethodSignature("setTheme", VOID, INT)); + + private boolean mShouldTransform; + private String mClassName; + private String mSuperClassName; + private ClassLoader mClassLoader; + + CustomResourcesClassAdapter(ClassVisitor visitor, String className, String superClassName, + ClassLoader classLoader) { + super(ASM5, visitor); + this.mClassName = className; + this.mSuperClassName = superClassName; + this.mClassLoader = classLoader; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, + String[] interfaces) { + super.visit(version, access, name, signature, superName, interfaces); + mShouldTransform = shouldTransform(); + } + + @Override + public MethodVisitor visitMethod(final int access, final String name, String desc, + String signature, String[] exceptions) { + if (mShouldTransform) { + String methodSignature = name + desc; + if (requiresModifyingExisting(methodSignature)) { + throw new RuntimeException("Rewriting existing methods not supported: " + mClassName + + "#" + methodSignature); + } + } + return new RewriteGetIdentifierMethodVisitor( + super.visitMethod(access, name, desc, signature, exceptions)); + } + + @Override + public void visitEnd() { + if (mShouldTransform) { + delegateCreateConfigurationContext(); + delegateSetTheme(); + delegateGet("getAssets", ASSET_MANAGER); + delegateGet("getTheme", THEME); + delegateGet("getResources", RESOURCES); + } + super.visitEnd(); + } + + private boolean requiresModifyingExisting(String methodDescriptor) { + return PROHIBITED_METHODS.contains(methodDescriptor); + } + + private boolean shouldTransform() { + if (!isDescendantOfContext()) { + return false; + } + if (!superClassIsFrameworkClass()) { + return false; + } + return !superClassIsContextWrapper(); + } + + private boolean superClassIsFrameworkClass() { + return loadClass(mSuperClassName).getProtectionDomain().toString().contains("android.jar"); + } + + private boolean isDescendantOfContext() { + return isSubClass(mClassName, CONTEXT); + } + + private boolean superClassIsContextWrapper() { + return mSuperClassName.equals(CONTEXT_WRAPPER); + } + + private boolean isSubClass(String candidate, String other) { + Class<?> candidateClazz = loadClass(candidate); + Class<?> parentClazz = loadClass(other); + return parentClazz.isAssignableFrom(candidateClazz); + } + + private Class<?> loadClass(String className) { + try { + return mClassLoader.loadClass(className.replace('/', '.')); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + /** + * Remaps Resources.getIdentifier() method calls to use BuildHooksAndroid. + * + * resourceObj.getIdentifier(String, String, String) becomes: + * BuildHooksAndroid.getIdentifier(resourceObj, String, String, String); + */ + private static final class RewriteGetIdentifierMethodVisitor extends MethodVisitor { + RewriteGetIdentifierMethodVisitor(MethodVisitor mv) { + super(ASM5, mv); + } + + @Override + public void visitMethodInsn( + int opcode, String owner, String name, String desc, boolean itf) { + String methodName = "getIdentifier"; + if (opcode == INVOKEVIRTUAL && owner.equals(RESOURCES) && name.equals(methodName) + && desc.equals(GET_IDENTIFIER_DESCRIPTOR)) { + super.visitMethodInsn(INVOKESTATIC, BUILD_HOOKS_ANDROID, methodName, + TypeUtils.getMethodDescriptor(INT, RESOURCES, STRING, STRING, STRING), itf); + } else { + super.visitMethodInsn(opcode, owner, name, desc, itf); + } + } + } + + /** + * Generates: + * + * <pre> + * public Context createConfigurationContext(Configuration configuration) { + * // createConfigurationContext does not exist before API level 17. + * if (Build.VERSION.SDK_INT < 17) return null; + * if (!BuildHooksAndroid.isEnabled()) return super.createConfigurationContext(configuration); + * return BuildHooksAndroid.createConfigurationContext( + * super.createConfigurationContext(configuration)); + * } + * </pre> + * } + */ + private void delegateCreateConfigurationContext() { + String methodName = "createConfigurationContext"; + String methodDescriptor = TypeUtils.getMethodDescriptor(CONTEXT, CONFIGURATION); + MethodVisitor mv = super.visitMethod(ACC_PUBLIC, methodName, methodDescriptor, null, null); + mv.visitCode(); + mv.visitFieldInsn(GETSTATIC, "android/os/Build$VERSION", "SDK_INT", INT); + mv.visitIntInsn(BIPUSH, 17); + Label l0 = new Label(); + mv.visitJumpInsn(IF_ICMPGE, l0); + mv.visitInsn(ACONST_NULL); + mv.visitInsn(ARETURN); + mv.visitLabel(l0); + mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + mv.visitMethodInsn( + INVOKESTATIC, BUILD_HOOKS_ANDROID, IS_ENABLED_METHOD, IS_ENABLED_DESCRIPTOR, false); + Label l1 = new Label(); + mv.visitJumpInsn(IFNE, l1); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 1); + mv.visitMethodInsn(INVOKESPECIAL, mSuperClassName, methodName, methodDescriptor, false); + mv.visitInsn(ARETURN); + mv.visitLabel(l1); + mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 1); + mv.visitMethodInsn(INVOKESPECIAL, mSuperClassName, methodName, methodDescriptor, false); + mv.visitMethodInsn(INVOKESTATIC, BUILD_HOOKS_ANDROID, methodName, + TypeUtils.getMethodDescriptor(CONTEXT, CONTEXT), false); + mv.visitInsn(ARETURN); + mv.visitMaxs(2, 2); + mv.visitEnd(); + } + + /** + * Generates: + * + * <pre> + * public void setTheme(int theme) { + * if (!BuildHooksAndroid.isEnabled()) { + * super.setTheme(theme); + * return; + * } + * BuildHooksAndroid.setTheme(this, theme); + * } + * </pre> + */ + private void delegateSetTheme() { + String methodName = "setTheme"; + String methodDescriptor = TypeUtils.getMethodDescriptor(VOID, INT); + String buildHooksMethodDescriptor = TypeUtils.getMethodDescriptor(VOID, CONTEXT, INT); + MethodVisitor mv = super.visitMethod(ACC_PUBLIC, methodName, methodDescriptor, null, null); + mv.visitCode(); + mv.visitMethodInsn( + INVOKESTATIC, BUILD_HOOKS_ANDROID, IS_ENABLED_METHOD, IS_ENABLED_DESCRIPTOR, false); + Label l0 = new Label(); + mv.visitJumpInsn(IFNE, l0); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ILOAD, 1); + mv.visitMethodInsn(INVOKESPECIAL, mSuperClassName, methodName, methodDescriptor, false); + mv.visitInsn(RETURN); + mv.visitLabel(l0); + mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ILOAD, 1); + mv.visitMethodInsn( + INVOKESTATIC, BUILD_HOOKS_ANDROID, methodName, buildHooksMethodDescriptor, false); + mv.visitInsn(RETURN); + mv.visitMaxs(2, 2); + mv.visitEnd(); + } + + /** + * Generates: + * + * <pre> + * public returnType methodName() { + * if (!BuildHooksAndroid.isEnabled()) return super.methodName(); + * return BuildHooksAndroid.methodName(this); + * } + * </pre> + */ + private void delegateGet(String methodName, String returnType) { + String getMethodDescriptor = TypeUtils.getMethodDescriptor(returnType); + String buildHooksGetMethodDescriptor = TypeUtils.getMethodDescriptor(returnType, CONTEXT); + MethodVisitor mv = + super.visitMethod(ACC_PUBLIC, methodName, getMethodDescriptor, null, null); + mv.visitCode(); + mv.visitMethodInsn( + INVOKESTATIC, BUILD_HOOKS_ANDROID, IS_ENABLED_METHOD, IS_ENABLED_DESCRIPTOR, false); + Label l0 = new Label(); + mv.visitJumpInsn(IFNE, l0); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, mSuperClassName, methodName, getMethodDescriptor, false); + mv.visitInsn(ARETURN); + mv.visitLabel(l0); + mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESTATIC, BUILD_HOOKS_ANDROID, methodName, + buildHooksGetMethodDescriptor, false); + mv.visitInsn(ARETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + } +} diff --git a/deps/v8/build/android/bytecode/java/org/chromium/bytecode/SplitCompatClassAdapter.java b/deps/v8/build/android/bytecode/java/org/chromium/bytecode/SplitCompatClassAdapter.java new file mode 100644 index 0000000000..8d6ae69483 --- /dev/null +++ b/deps/v8/build/android/bytecode/java/org/chromium/bytecode/SplitCompatClassAdapter.java @@ -0,0 +1,149 @@ +// Copyright 2019 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_PROTECTED; +import static org.objectweb.asm.Opcodes.ALOAD; +import static org.objectweb.asm.Opcodes.INVOKESPECIAL; +import static org.objectweb.asm.Opcodes.INVOKESTATIC; +import static org.objectweb.asm.Opcodes.RETURN; + +import static org.chromium.bytecode.TypeUtils.CONTEXT; +import static org.chromium.bytecode.TypeUtils.VOID; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import java.util.Set; + +/** + * A ClassVisitor for injecting ModuleInstaller.initActivity(activity) method call + * into Activity's attachBaseContext() method. The goal is to eventually invoke + * SplitCompat.install() method if running with the binary that has bundle support + * enabled. This needs to happen for activities that were not built with SplitCompat + * support. + */ +class SplitCompatClassAdapter extends ClassVisitor { + private static final String ANDROID_APP_ACTIVITY_CLASS_NAME = "android/app/Activity"; + private static final String ATTACH_BASE_CONTEXT_METHOD_NAME = "attachBaseContext"; + private static final String ATTACH_BASE_CONTEXT_DESCRIPTOR = + TypeUtils.getMethodDescriptor(VOID, CONTEXT); + + private static final String MODULE_INSTALLER_CLASS_NAME = + "org/chromium/components/module_installer/ModuleInstaller"; + private static final String INIT_ACTIVITY_METHOD_NAME = "initActivity"; + private static final String INIT_ACTIVITY_DESCRIPTOR = + TypeUtils.getMethodDescriptor(VOID, CONTEXT); + + private boolean mShouldTransform; + + private Set<String> mClassNames; + + private ClassLoader mClassLoader; + + /** + * Creates instance of SplitCompatClassAdapter. + * + * @param visitor + * @param classNames Names of classes into which the attachBaseContext method will be + * injected. Currently, we'll only consider classes for bytecode rewriting only if + * they inherit directly from android.app.Activity & not already contain + * attachBaseContext method. + * @param classLoader + */ + SplitCompatClassAdapter(ClassVisitor visitor, Set<String> classNames, ClassLoader classLoader) { + super(Opcodes.ASM5, visitor); + + mShouldTransform = false; + mClassNames = classNames; + mClassLoader = classLoader; + } + + @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 (mClassNames.contains(name)) { + if (!isSubclassOfActivity(name)) { + throw new RuntimeException(name + + " should be transformed but does not inherit from android.app.Activity"); + } + + mShouldTransform = true; + } + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String descriptor, String signature, String[] exceptions) { + // Check if current method matches attachBaseContext & we're supposed to emit code - if so, + // fail. + if (mShouldTransform && name.equals(ATTACH_BASE_CONTEXT_METHOD_NAME)) { + throw new RuntimeException(ATTACH_BASE_CONTEXT_METHOD_NAME + " method already exists"); + } + + return super.visitMethod(access, name, descriptor, signature, exceptions); + } + + @Override + public void visitEnd() { + if (mShouldTransform) { + // If we reached this place, it means we're rewriting a class that inherits from + // Activity and there was no exception thrown due to existence of attachBaseContext + // method - emit code. + emitAttachBaseContext(); + } + + super.visitEnd(); + } + + /** + * Generates: + * + * <pre> + * protected void attachBaseContext(Context base) { + * super.attachBaseContext(base); + * ModuleInstaller.initActivity(this); + * } + * </pre> + */ + private void emitAttachBaseContext() { + MethodVisitor mv = super.visitMethod(ACC_PROTECTED, ATTACH_BASE_CONTEXT_METHOD_NAME, + ATTACH_BASE_CONTEXT_DESCRIPTOR, null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); // load "this" on stack + mv.visitVarInsn(ALOAD, 1); // load first method parameter on stack (Context) + mv.visitMethodInsn(INVOKESPECIAL, ANDROID_APP_ACTIVITY_CLASS_NAME, + ATTACH_BASE_CONTEXT_METHOD_NAME, + ATTACH_BASE_CONTEXT_DESCRIPTOR); // invoke super's attach base context + mv.visitVarInsn(ALOAD, 0); // load "this" on stack + mv.visitMethodInsn(INVOKESTATIC, MODULE_INSTALLER_CLASS_NAME, INIT_ACTIVITY_METHOD_NAME, + INIT_ACTIVITY_DESCRIPTOR); + mv.visitInsn(RETURN); + mv.visitMaxs(2, 2); // max stack size - 2, max locals - 2 + mv.visitEnd(); + } + + /** + * Checks whether passed in class inherits from android.app.Activity. + * @param name Name of the class to be checked. + * @return true if class inherits from android.app.Activity, false otherwise. + */ + private boolean isSubclassOfActivity(String name) { + Class<?> activityClass = loadClass(ANDROID_APP_ACTIVITY_CLASS_NAME); + Class<?> candidateClass = loadClass(name); + return activityClass.isAssignableFrom(candidateClass); + } + + private Class<?> loadClass(String className) { + try { + return mClassLoader.loadClass(className.replace('/', '.')); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } +} diff --git a/deps/v8/build/android/bytecode/java/org/chromium/bytecode/ThreadAssertionClassAdapter.java b/deps/v8/build/android/bytecode/java/org/chromium/bytecode/ThreadAssertionClassAdapter.java new file mode 100644 index 0000000000..3f50b25f3e --- /dev/null +++ b/deps/v8/build/android/bytecode/java/org/chromium/bytecode/ThreadAssertionClassAdapter.java @@ -0,0 +1,83 @@ +// 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 static org.objectweb.asm.Opcodes.ASM5; +import static org.objectweb.asm.Opcodes.INVOKESTATIC; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; + +/** + * A ClassVisitor which adds calls to + * {@link org.chromium.base.ThreadUtils}'s assertOnUiThread/assertOnBackgroundThread when the + * corresponding {@link android.support.annotation.UiThread} or + * {@link android.support.annotation.WorkerThread} annotations are present. The function calls + * are placed at the start of the method. + */ +class ThreadAssertionClassAdapter extends ClassVisitor { + private static final String THREAD_UTILS_DESCRIPTOR = "org/chromium/base/ThreadUtils"; + private static final String THREAD_UTILS_SIGNATURE = "()V"; + private static final String UI_THREAD_ANNOTATION_DESCRIPTOR = + "Landroid/support/annotation/UiThread;"; + private static final String WORKER_THREAD_ANNOTATION_DESCRIPTOR = + "Landroid/support/annotation/WorkerThread;"; + + ThreadAssertionClassAdapter(ClassVisitor visitor) { + super(ASM5, visitor); + } + + @Override + public MethodVisitor visitMethod(final int access, final String name, String desc, + String signature, String[] exceptions) { + return new AddAssertMethodVisitor( + super.visitMethod(access, name, desc, signature, exceptions)); + } + + private static class AddAssertMethodVisitor extends MethodVisitor { + String mAssertMethodName = ""; + + AddAssertMethodVisitor(MethodVisitor mv) { + super(ASM5, mv); + } + + /** + * Call for annotations on the method. Checks if the annotation is @UiThread + * or @WorkerThread, and if so will set the mAssertMethodName property to the name of the + * method to call in order to assert that a method is running on the intented thread. + * + * @param descriptor Annotation descriptor containing its name and package. + */ + @Override + public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { + switch (descriptor) { + case UI_THREAD_ANNOTATION_DESCRIPTOR: + mAssertMethodName = "assertOnUiThread"; + break; + case WORKER_THREAD_ANNOTATION_DESCRIPTOR: + mAssertMethodName = "assertOnBackgroundThread"; + break; + default: + break; + } + + return super.visitAnnotation(descriptor, visible); + } + + /** + * Called to start visiting code. Will also insert the assertOnXThread methods at the start + * of the method if needed. + */ + @Override + public void visitCode() { + super.visitCode(); + if (!mAssertMethodName.equals("")) { + visitMethodInsn(INVOKESTATIC, THREAD_UTILS_DESCRIPTOR, mAssertMethodName, + THREAD_UTILS_SIGNATURE, false); + } + } + } +}
\ No newline at end of file diff --git a/deps/v8/build/android/bytecode/java/org/chromium/bytecode/TypeUtils.java b/deps/v8/build/android/bytecode/java/org/chromium/bytecode/TypeUtils.java new file mode 100644 index 0000000000..ed2dc2dc24 --- /dev/null +++ b/deps/v8/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); + } +} |