| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.dx; |
| |
| import com.android.dex.DexFormat; |
| import com.android.dx.dex.DexOptions; |
| import com.android.dx.dex.code.DalvCode; |
| import com.android.dx.dex.code.PositionList; |
| import com.android.dx.dex.code.RopTranslator; |
| import com.android.dx.dex.file.ClassDefItem; |
| import com.android.dx.dex.file.DexFile; |
| import com.android.dx.dex.file.EncodedField; |
| import com.android.dx.dex.file.EncodedMethod; |
| import com.android.dx.rop.code.AccessFlags; |
| import com.android.dx.rop.code.LocalVariableInfo; |
| import com.android.dx.rop.code.RopMethod; |
| import com.android.dx.rop.cst.CstString; |
| import com.android.dx.rop.cst.CstType; |
| import com.android.dx.rop.type.StdTypeList; |
| |
| import java.io.BufferedOutputStream; |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Modifier; |
| import java.util.Arrays; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.jar.JarEntry; |
| import java.util.jar.JarOutputStream; |
| |
| import static com.android.dx.rop.code.AccessFlags.ACC_CONSTRUCTOR; |
| import static java.lang.reflect.Modifier.*; |
| |
| /** |
| * Generates a <strong>D</strong>alvik <strong>EX</strong>ecutable (dex) |
| * file for execution on Android. Dex files define classes and interfaces, |
| * including their member methods and fields, executable code, and debugging |
| * information. They also define annotations, though this API currently has no |
| * facility to create a dex file that contains annotations. |
| * |
| * <p>This library is intended to satisfy two use cases: |
| * <ul> |
| * <li><strong>For runtime code generation.</strong> By embedding this library |
| * in your Android application, you can dynamically generate and load |
| * executable code. This approach takes advantage of the fact that the |
| * host environment and target environment are both Android. |
| * <li><strong>For compile time code generation.</strong> You may use this |
| * library as a part of a compiler that targets Android. In this scenario |
| * the generated dex file must be installed on an Android device before it |
| * can be executed. |
| * </ul> |
| * |
| * <h3>Example: Fibonacci</h3> |
| * To illustrate how this API is used, we'll use DexMaker to generate a class |
| * equivalent to the following Java source: <pre> {@code |
| * |
| * package com.publicobject.fib; |
| * |
| * public class Fibonacci { |
| * public static int fib(int i) { |
| * if (i < 2) { |
| * return i; |
| * } |
| * return fib(i - 1) + fib(i - 2); |
| * } |
| * }}</pre> |
| * |
| * <p>We start by creating a {@link TypeId} to identify the generated {@code |
| * Fibonacci} class. DexMaker identifies types by their internal names like |
| * {@code Ljava/lang/Object;} rather than their Java identifiers like {@code |
| * java.lang.Object}. <pre> {@code |
| * |
| * TypeId<?> fibonacci = TypeId.get("Lcom/google/dexmaker/examples/Fibonacci;"); |
| * }</pre> |
| * |
| * <p>Next we declare the class. It allows us to specify the type's source file |
| * for stack traces, its modifiers, its superclass, and the interfaces it |
| * implements. In this case, {@code Fibonacci} is a public class that extends |
| * from {@code Object}: <pre> {@code |
| * |
| * String fileName = "Fibonacci.generated"; |
| * DexMaker dexMaker = new DexMaker(); |
| * dexMaker.declare(fibonacci, fileName, Modifier.PUBLIC, TypeId.OBJECT); |
| * }</pre> |
| * It is illegal to declare members of a class without also declaring the class |
| * itself. |
| * |
| * <p>To make it easier to go from our Java method to dex instructions, we'll |
| * manually translate it to pseudocode fit for an assembler. We need to replace |
| * control flow like {@code if()} blocks and {@code for()} loops with labels and |
| * branches. We'll also avoid performing multiple operations in one statement, |
| * using local variables to hold intermediate values as necessary: |
| * <pre> {@code |
| * |
| * int constant1 = 1; |
| * int constant2 = 2; |
| * if (i < constant2) goto baseCase; |
| * int a = i - constant1; |
| * int b = i - constant2; |
| * int c = fib(a); |
| * int d = fib(b); |
| * int result = c + d; |
| * return result; |
| * baseCase: |
| * return i; |
| * }</pre> |
| * |
| * <p>We look up the {@code MethodId} for the method on the declaring type. This |
| * takes the method's return type (possibly {@link TypeId#VOID}), its name and |
| * its parameters types. Next we declare the method, specifying its modifiers by |
| * bitwise ORing constants from {@link java.lang.reflect.Modifier}. The declare |
| * call returns a {@link Code} object, which we'll use to define the method's |
| * instructions. <pre> {@code |
| * |
| * MethodId<?, Integer> fib = fibonacci.getMethod(TypeId.INT, "fib", TypeId.INT); |
| * Code code = dexMaker.declare(fib, Modifier.PUBLIC | Modifier.STATIC); |
| * }</pre> |
| * |
| * <p>One limitation of {@code DexMaker}'s API is that it requires all local |
| * variables to be created before any instructions are emitted. Use {@link |
| * Code#newLocal newLocal()} to create a new local variable. The method's |
| * parameters are exposed as locals using {@link Code#getParameter |
| * getParameter()}. For non-static methods the {@code this} pointer is exposed |
| * using {@link Code#getThis getThis()}. Here we declare all of the local |
| * variables that we'll need for our {@code fib()} method: <pre> {@code |
| * |
| * Local<Integer> i = code.getParameter(0, TypeId.INT); |
| * Local<Integer> constant1 = code.newLocal(TypeId.INT); |
| * Local<Integer> constant2 = code.newLocal(TypeId.INT); |
| * Local<Integer> a = code.newLocal(TypeId.INT); |
| * Local<Integer> b = code.newLocal(TypeId.INT); |
| * Local<Integer> c = code.newLocal(TypeId.INT); |
| * Local<Integer> d = code.newLocal(TypeId.INT); |
| * Local<Integer> result = code.newLocal(TypeId.INT); |
| * }</pre> |
| * |
| * <p>Notice that {@link Local} has a type parameter of {@code Integer}. This is |
| * useful for generating code that works with existing types like {@code String} |
| * and {@code Integer}, but it can be a hindrance when generating code that |
| * involves new types. For this reason you may prefer to use raw types only and |
| * add {@code @SuppressWarnings("unsafe")} on your calling code. This will yield |
| * the same result but you won't get IDE support if you make a type error. |
| * |
| * <p>We're ready to start defining our method's instructions. The {@link Code} |
| * class catalogs the available instructions and their use. <pre> {@code |
| * |
| * code.loadConstant(constant1, 1); |
| * code.loadConstant(constant2, 2); |
| * Label baseCase = new Label(); |
| * code.compare(Comparison.LT, baseCase, i, constant2); |
| * code.op(BinaryOp.SUBTRACT, a, i, constant1); |
| * code.op(BinaryOp.SUBTRACT, b, i, constant2); |
| * code.invokeStatic(fib, c, a); |
| * code.invokeStatic(fib, d, b); |
| * code.op(BinaryOp.ADD, result, c, d); |
| * code.returnValue(result); |
| * code.mark(baseCase); |
| * code.returnValue(i); |
| * }</pre> |
| * |
| * <p>We're done defining the dex file. We just need to write it to the |
| * filesystem or load it into the current process. For this example we'll load |
| * the generated code into the current process. This only works when the current |
| * process is running on Android. We use {@link #generateAndLoad |
| * generateAndLoad()} which takes the class loader that will be used as our |
| * generated code's parent class loader. It also requires a directory where |
| * temporary files can be written. <pre> {@code |
| * |
| * ClassLoader loader = dexMaker.generateAndLoad( |
| * FibonacciMaker.class.getClassLoader(), getDataDirectory()); |
| * }</pre> |
| * Finally we'll use reflection to lookup our generated class on its class |
| * loader and invoke its {@code fib()} method: <pre> {@code |
| * |
| * Class<?> fibonacciClass = loader.loadClass("com.google.dexmaker.examples.Fibonacci"); |
| * Method fibMethod = fibonacciClass.getMethod("fib", int.class); |
| * System.out.println(fibMethod.invoke(null, 8)); |
| * }</pre> |
| */ |
| public final class DexMaker { |
| private final Map<TypeId<?>, TypeDeclaration> types = new LinkedHashMap<>(); |
| |
| // Only warn about not being able to deal with blacklisted methods once. Often this is no |
| // problem and warning on every class load is too spammy. |
| private static boolean didWarnBlacklistedMethods; |
| private static boolean didWarnNonBaseDexClassLoader; |
| |
| private ClassLoader sharedClassLoader; |
| private DexFile outputDex; |
| private boolean markAsTrusted; |
| |
| /** |
| * Creates a new {@code DexMaker} instance, which can be used to create a |
| * single dex file. |
| */ |
| public DexMaker() { |
| } |
| |
| TypeDeclaration getTypeDeclaration(TypeId<?> type) { |
| TypeDeclaration result = types.get(type); |
| if (result == null) { |
| result = new TypeDeclaration(type); |
| types.put(type, result); |
| } |
| return result; |
| } |
| |
| /** |
| * Declares {@code type}. |
| * |
| * @param flags a bitwise combination of {@link Modifier#PUBLIC}, {@link |
| * Modifier#FINAL} and {@link Modifier#ABSTRACT}. |
| */ |
| public void declare(TypeId<?> type, String sourceFile, int flags, |
| TypeId<?> supertype, TypeId<?>... interfaces) { |
| TypeDeclaration declaration = getTypeDeclaration(type); |
| int supportedFlags = Modifier.PUBLIC | Modifier.FINAL | Modifier.ABSTRACT |
| | AccessFlags.ACC_SYNTHETIC; |
| if ((flags & ~supportedFlags) != 0) { |
| throw new IllegalArgumentException("Unexpected flag: " |
| + Integer.toHexString(flags)); |
| } |
| if (declaration.declared) { |
| throw new IllegalStateException("already declared: " + type); |
| } |
| declaration.declared = true; |
| declaration.flags = flags; |
| declaration.supertype = supertype; |
| declaration.sourceFile = sourceFile; |
| declaration.interfaces = new TypeList(interfaces); |
| } |
| |
| /** |
| * Declares a method or constructor. |
| * |
| * @param flags a bitwise combination of {@link Modifier#PUBLIC}, {@link |
| * Modifier#PRIVATE}, {@link Modifier#PROTECTED}, {@link Modifier#STATIC}, |
| * {@link Modifier#FINAL} and {@link Modifier#SYNCHRONIZED}. |
| * <p><strong>Warning:</strong> the {@link Modifier#SYNCHRONIZED} flag |
| * is insufficient to generate a synchronized method. You must also use |
| * {@link Code#monitorEnter} and {@link Code#monitorExit} to acquire |
| * a monitor. |
| */ |
| public Code declare(MethodId<?, ?> method, int flags) { |
| TypeDeclaration typeDeclaration = getTypeDeclaration(method.declaringType); |
| if (typeDeclaration.methods.containsKey(method)) { |
| throw new IllegalStateException("already declared: " + method); |
| } |
| |
| int supportedFlags = Modifier.ABSTRACT | Modifier.NATIVE | Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED |
| | Modifier.STATIC | Modifier.FINAL | Modifier.SYNCHRONIZED |
| | AccessFlags.ACC_SYNTHETIC | AccessFlags.ACC_BRIDGE; |
| if ((flags & ~supportedFlags) != 0) { |
| throw new IllegalArgumentException("Unexpected flag: " |
| + Integer.toHexString(flags)); |
| } |
| |
| // replace the SYNCHRONIZED flag with the DECLARED_SYNCHRONIZED flag |
| if ((flags & Modifier.SYNCHRONIZED) != 0) { |
| flags = (flags & ~Modifier.SYNCHRONIZED) | AccessFlags.ACC_DECLARED_SYNCHRONIZED; |
| } |
| |
| if (method.isConstructor() || method.isStaticInitializer()) { |
| flags |= ACC_CONSTRUCTOR; |
| } |
| |
| MethodDeclaration methodDeclaration = new MethodDeclaration(method, flags); |
| typeDeclaration.methods.put(method, methodDeclaration); |
| return methodDeclaration.code; |
| } |
| |
| /** |
| * Declares a field. |
| * |
| * @param flags a bitwise combination of {@link Modifier#PUBLIC}, {@link |
| * Modifier#PRIVATE}, {@link Modifier#PROTECTED}, {@link Modifier#STATIC}, |
| * {@link Modifier#FINAL}, {@link Modifier#VOLATILE}, and {@link |
| * Modifier#TRANSIENT}. |
| * @param staticValue a constant representing the initial value for the |
| * static field, possibly null. This must be null if this field is |
| * non-static. |
| */ |
| public void declare(FieldId<?, ?> fieldId, int flags, Object staticValue) { |
| TypeDeclaration typeDeclaration = getTypeDeclaration(fieldId.declaringType); |
| if (typeDeclaration.fields.containsKey(fieldId)) { |
| throw new IllegalStateException("already declared: " + fieldId); |
| } |
| |
| int supportedFlags = Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED |
| | Modifier.STATIC | Modifier.FINAL | Modifier.VOLATILE | Modifier.TRANSIENT |
| | AccessFlags.ACC_SYNTHETIC; |
| if ((flags & ~supportedFlags) != 0) { |
| throw new IllegalArgumentException("Unexpected flag: " |
| + Integer.toHexString(flags)); |
| } |
| |
| if ((flags & Modifier.STATIC) == 0 && staticValue != null) { |
| throw new IllegalArgumentException("staticValue is non-null, but field is not static"); |
| } |
| |
| FieldDeclaration fieldDeclaration = new FieldDeclaration(fieldId, flags, staticValue); |
| typeDeclaration.fields.put(fieldId, fieldDeclaration); |
| } |
| |
| /** |
| * Generates a dex file and returns its bytes. |
| */ |
| public byte[] generate() { |
| if (outputDex == null) { |
| DexOptions options = new DexOptions(); |
| options.minSdkVersion = DexFormat.API_NO_EXTENDED_OPCODES; |
| outputDex = new DexFile(options); |
| } |
| |
| for (TypeDeclaration typeDeclaration : types.values()) { |
| outputDex.add(typeDeclaration.toClassDefItem()); |
| } |
| |
| try { |
| return outputDex.toDex(null, false); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| // Generate a file name for the jar by taking a checksum of MethodIds and |
| // parent class types. |
| private String generateFileName() { |
| int checksum = 1; |
| |
| Set<TypeId<?>> typesKeySet = types.keySet(); |
| Iterator<TypeId<?>> it = typesKeySet.iterator(); |
| int[] checksums = new int[typesKeySet.size()]; |
| int i = 0; |
| |
| while (it.hasNext()) { |
| TypeId<?> typeId = it.next(); |
| TypeDeclaration decl = getTypeDeclaration(typeId); |
| Set<MethodId> methodSet = decl.methods.keySet(); |
| if (decl.supertype != null) { |
| int sum = 31 * decl.supertype.hashCode() + decl.interfaces.hashCode(); |
| checksums[i++] = 31 * sum + methodSet.hashCode(); |
| } |
| } |
| Arrays.sort(checksums); |
| |
| for (int sum : checksums) { |
| checksum *= 31; |
| checksum += sum; |
| } |
| |
| return "Generated_" + checksum +".jar"; |
| } |
| |
| /** |
| * Set shared class loader to use. |
| * |
| * <p>If a class wants to call package private methods of another class they need to share a |
| * class loader. One common case for this requirement is a mock class wanting to mock package |
| * private methods of the original class. |
| * |
| * <p>If the classLoader is not a subclass of {@code dalvik.system.BaseDexClassLoader} this |
| * option is ignored. |
| * |
| * @param classLoader the class loader the new class should be loaded by |
| */ |
| public void setSharedClassLoader(ClassLoader classLoader) { |
| this.sharedClassLoader = classLoader; |
| } |
| |
| public void markAsTrusted() { |
| this.markAsTrusted = true; |
| } |
| |
| private ClassLoader generateClassLoader(File result, File dexCache, ClassLoader parent) { |
| try { |
| boolean shareClassLoader = sharedClassLoader != null; |
| |
| ClassLoader preferredClassLoader = null; |
| if (parent != null) { |
| preferredClassLoader = parent; |
| } else if (sharedClassLoader != null) { |
| preferredClassLoader = sharedClassLoader; |
| } |
| |
| Class baseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader"); |
| |
| if (shareClassLoader) { |
| if (!baseDexClassLoaderClass.isAssignableFrom(preferredClassLoader.getClass())) { |
| if (!preferredClassLoader.getClass().getName().equals( |
| "java.lang.BootClassLoader")) { |
| if (!didWarnNonBaseDexClassLoader) { |
| System.err.println("Cannot share classloader as shared classloader '" |
| + preferredClassLoader + "' is not a subclass of '" |
| + baseDexClassLoaderClass |
| + "'"); |
| didWarnNonBaseDexClassLoader = true; |
| } |
| } |
| |
| shareClassLoader = false; |
| } |
| } |
| |
| // Try to load the class so that it can call hidden APIs. This is required for spying |
| // on system classes as real-methods of these classes might call blacklisted APIs |
| if (markAsTrusted) { |
| try { |
| if (shareClassLoader) { |
| preferredClassLoader.getClass().getMethod("addDexPath", String.class, |
| Boolean.TYPE).invoke(preferredClassLoader, result.getPath(), true); |
| return preferredClassLoader; |
| } else { |
| return (ClassLoader) baseDexClassLoaderClass |
| .getConstructor(String.class, File.class, String.class, |
| ClassLoader.class, Boolean.TYPE) |
| .newInstance(result.getPath(), dexCache.getAbsoluteFile(), null, |
| preferredClassLoader, true); |
| } |
| } catch (InvocationTargetException e) { |
| if (e.getCause() instanceof SecurityException) { |
| if (!didWarnBlacklistedMethods) { |
| System.err.println("Cannot allow to call blacklisted super methods. " |
| + "This might break spying on system classes." + e.getCause()); |
| didWarnBlacklistedMethods = true; |
| } |
| } else { |
| throw e; |
| } |
| } |
| } |
| |
| if (shareClassLoader) { |
| preferredClassLoader.getClass().getMethod("addDexPath", String.class).invoke( |
| preferredClassLoader, result.getPath()); |
| return preferredClassLoader; |
| } else { |
| return (ClassLoader) Class.forName("dalvik.system.DexClassLoader") |
| .getConstructor(String.class, String.class, String.class, ClassLoader.class) |
| .newInstance(result.getPath(), dexCache.getAbsolutePath(), null, |
| preferredClassLoader); |
| } |
| } catch (ClassNotFoundException e) { |
| throw new UnsupportedOperationException("load() requires a Dalvik VM", e); |
| } catch (InvocationTargetException e) { |
| throw new RuntimeException(e.getCause()); |
| } catch (InstantiationException e) { |
| throw new AssertionError(); |
| } catch (NoSuchMethodException e) { |
| throw new AssertionError(); |
| } catch (IllegalAccessException e) { |
| throw new AssertionError(); |
| } |
| } |
| |
| /** |
| * Generates a dex file and loads its types into the current process. |
| * |
| * <h3>Picking a dex cache directory</h3> |
| * The {@code dexCache} should be an application-private directory. If |
| * you pass a world-writable directory like {@code /sdcard} a malicious app |
| * could inject code into your process. Most applications should use this: |
| * <pre> {@code |
| * |
| * File dexCache = getApplicationContext().getDir("dx", Context.MODE_PRIVATE); |
| * }</pre> |
| * If the {@code dexCache} is null, this method will consult the {@code |
| * dexmaker.dexcache} system property. If that exists, it will be used for |
| * the dex cache. If it doesn't exist, this method will attempt to guess |
| * the application's private data directory as a last resort. If that fails, |
| * this method will fail with an unchecked exception. You can avoid the |
| * exception by either providing a non-null value or setting the system |
| * property. |
| * |
| * @param parent the parent ClassLoader to be used when loading our |
| * generated types (if set, overrides |
| * {@link #setSharedClassLoader(ClassLoader) shared class loader}. |
| * @param dexCache the destination directory where generated and optimized |
| * dex files will be written. If null, this class will try to guess the |
| * application's private data dir. |
| */ |
| public ClassLoader generateAndLoad(ClassLoader parent, File dexCache) throws IOException { |
| if (dexCache == null) { |
| String property = System.getProperty("dexmaker.dexcache"); |
| if (property != null) { |
| dexCache = new File(property); |
| } else { |
| dexCache = new AppDataDirGuesser().guess(); |
| if (dexCache == null) { |
| throw new IllegalArgumentException("dexcache == null (and no default could be" |
| + " found; consider setting the 'dexmaker.dexcache' system property)"); |
| } |
| } |
| } |
| |
| File result = new File(dexCache, generateFileName()); |
| // Check that the file exists. If it does, return a DexClassLoader and skip all |
| // the dex bytecode generation. |
| if (result.exists()) { |
| if (!result.canWrite()) { |
| return generateClassLoader(result, dexCache, parent); |
| } else { |
| // Old writable files should be ignored and re-generated |
| result.delete(); |
| } |
| } |
| |
| byte[] dex = generate(); |
| |
| /* |
| * This implementation currently dumps the dex to the filesystem. It |
| * jars the emitted .dex for the benefit of Gingerbread and earlier |
| * devices, which can't load .dex files directly. |
| * |
| * TODO: load the dex from memory where supported. |
| */ |
| |
| JarOutputStream jarOut = |
| new JarOutputStream(new BufferedOutputStream(new FileOutputStream(result))); |
| result.setReadOnly(); |
| try { |
| JarEntry entry = new JarEntry(DexFormat.DEX_IN_JAR_NAME); |
| entry.setSize(dex.length); |
| jarOut.putNextEntry(entry); |
| try { |
| jarOut.write(dex); |
| } finally { |
| jarOut.closeEntry(); |
| } |
| } finally { |
| jarOut.close(); |
| } |
| |
| return generateClassLoader(result, dexCache, parent); |
| } |
| |
| DexFile getDexFile() { |
| if (outputDex == null) { |
| DexOptions options = new DexOptions(); |
| options.minSdkVersion = DexFormat.API_NO_EXTENDED_OPCODES; |
| outputDex = new DexFile(options); |
| } |
| return outputDex; |
| } |
| |
| static class TypeDeclaration { |
| private final TypeId<?> type; |
| |
| /** declared state */ |
| private boolean declared; |
| private int flags; |
| private TypeId<?> supertype; |
| private String sourceFile; |
| private TypeList interfaces; |
| private ClassDefItem classDefItem; |
| |
| private final Map<FieldId, FieldDeclaration> fields = new LinkedHashMap<>(); |
| private final Map<MethodId, MethodDeclaration> methods = new LinkedHashMap<>(); |
| |
| TypeDeclaration(TypeId<?> type) { |
| this.type = type; |
| } |
| |
| ClassDefItem toClassDefItem() { |
| if (!declared) { |
| throw new IllegalStateException("Undeclared type " + type + " declares members: " |
| + fields.keySet() + " " + methods.keySet()); |
| } |
| |
| DexOptions dexOptions = new DexOptions(); |
| dexOptions.minSdkVersion = DexFormat.API_NO_EXTENDED_OPCODES; |
| |
| CstType thisType = type.constant; |
| |
| if (classDefItem == null) { |
| classDefItem = new ClassDefItem(thisType, flags, supertype.constant, |
| interfaces.ropTypes, new CstString(sourceFile)); |
| |
| for (MethodDeclaration method : methods.values()) { |
| EncodedMethod encoded = method.toEncodedMethod(dexOptions); |
| if (method.isDirect()) { |
| classDefItem.addDirectMethod(encoded); |
| } else { |
| classDefItem.addVirtualMethod(encoded); |
| } |
| } |
| for (FieldDeclaration field : fields.values()) { |
| EncodedField encoded = field.toEncodedField(); |
| if (field.isStatic()) { |
| classDefItem.addStaticField(encoded, Constants.getConstant(field.staticValue)); |
| } else { |
| classDefItem.addInstanceField(encoded); |
| } |
| } |
| } |
| |
| return classDefItem; |
| } |
| } |
| |
| static class FieldDeclaration { |
| final FieldId<?, ?> fieldId; |
| private final int accessFlags; |
| private final Object staticValue; |
| |
| FieldDeclaration(FieldId<?, ?> fieldId, int accessFlags, Object staticValue) { |
| if ((accessFlags & STATIC) == 0 && staticValue != null) { |
| throw new IllegalArgumentException("instance fields may not have a value"); |
| } |
| this.fieldId = fieldId; |
| this.accessFlags = accessFlags; |
| this.staticValue = staticValue; |
| } |
| |
| EncodedField toEncodedField() { |
| return new EncodedField(fieldId.constant, accessFlags); |
| } |
| |
| public boolean isStatic() { |
| return (accessFlags & STATIC) != 0; |
| } |
| } |
| |
| static class MethodDeclaration { |
| final MethodId<?, ?> method; |
| private final int flags; |
| private final Code code; |
| |
| public MethodDeclaration(MethodId<?, ?> method, int flags) { |
| this.method = method; |
| this.flags = flags; |
| this.code = new Code(this); |
| } |
| |
| boolean isStatic() { |
| return (flags & STATIC) != 0; |
| } |
| |
| boolean isDirect() { |
| return (flags & (STATIC | PRIVATE | ACC_CONSTRUCTOR)) != 0; |
| } |
| |
| EncodedMethod toEncodedMethod(DexOptions dexOptions) { |
| if((flags & ABSTRACT) != 0 || (flags & NATIVE) != 0){ |
| return new EncodedMethod(method.constant, flags, null, StdTypeList.EMPTY); |
| } |
| |
| RopMethod ropMethod = new RopMethod(code.toBasicBlocks(), 0); |
| LocalVariableInfo locals = null; |
| DalvCode dalvCode = RopTranslator.translate( |
| ropMethod, PositionList.NONE, locals, code.paramSize(), dexOptions); |
| return new EncodedMethod(method.constant, flags, dalvCode, StdTypeList.EMPTY); |
| } |
| } |
| } |