DexMaker.java revision ab220f004db90fa94ef9349ca1adde5f89012e8d
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.google.dexmaker; 18 19import com.android.dx.dex.DexFormat; 20import com.android.dx.dex.DexOptions; 21import com.android.dx.dex.code.DalvCode; 22import com.android.dx.dex.code.PositionList; 23import com.android.dx.dex.code.RopTranslator; 24import com.android.dx.dex.file.ClassDefItem; 25import com.android.dx.dex.file.DexFile; 26import com.android.dx.dex.file.EncodedField; 27import com.android.dx.dex.file.EncodedMethod; 28import com.android.dx.rop.code.AccessFlags; 29import static com.android.dx.rop.code.AccessFlags.ACC_CONSTRUCTOR; 30import com.android.dx.rop.code.LocalVariableInfo; 31import com.android.dx.rop.code.RopMethod; 32import com.android.dx.rop.cst.CstString; 33import com.android.dx.rop.cst.CstType; 34import com.android.dx.rop.type.StdTypeList; 35import java.io.File; 36import java.io.FileOutputStream; 37import java.io.IOException; 38import java.lang.reflect.InvocationTargetException; 39import static java.lang.reflect.Modifier.PRIVATE; 40import static java.lang.reflect.Modifier.STATIC; 41import java.util.LinkedHashMap; 42import java.util.Map; 43import java.util.jar.JarEntry; 44import java.util.jar.JarOutputStream; 45 46/** 47 * Define types, fields and methods. 48 */ 49public final class DexMaker { 50 private final Map<Type<?>, TypeDeclaration> types 51 = new LinkedHashMap<Type<?>, TypeDeclaration>(); 52 53 private TypeDeclaration getTypeDeclaration(Type<?> type) { 54 TypeDeclaration result = types.get(type); 55 if (result == null) { 56 result = new TypeDeclaration(type); 57 types.put(type, result); 58 } 59 return result; 60 } 61 62 // TODO: describe the legal flags without referring to a non-public API AccessFlags 63 64 /** 65 * @param flags any flags masked by {@link com.android.dx.rop.code.AccessFlags#CLASS_FLAGS}. 66 */ 67 public void declare(Type<?> type, String sourceFile, int flags, 68 Type<?> supertype, Type<?>... interfaces) { 69 TypeDeclaration declaration = getTypeDeclaration(type); 70 if (declaration.declared) { 71 throw new IllegalStateException("already declared: " + type); 72 } 73 declaration.declared = true; 74 declaration.flags = flags; 75 declaration.supertype = supertype; 76 declaration.sourceFile = sourceFile; 77 declaration.interfaces = new TypeList(interfaces); 78 } 79 80 /** 81 * @param flags any flags masked by {@link com.android.dx.rop.code.AccessFlags#METHOD_FLAGS}. 82 */ 83 public Code declareConstructor(MethodId<?, ?> method, int flags) { 84 return declare(method, flags | ACC_CONSTRUCTOR); 85 } 86 87 /** 88 * @param flags any flags masked by {@link com.android.dx.rop.code.AccessFlags#METHOD_FLAGS}. 89 */ 90 public Code declare(MethodId<?, ?> method, int flags) { 91 TypeDeclaration typeDeclaration = getTypeDeclaration(method.declaringType); 92 if (typeDeclaration.methods.containsKey(method)) { 93 throw new IllegalStateException("already declared: " + method); 94 } 95 MethodDeclaration methodDeclaration = new MethodDeclaration(method, flags); 96 typeDeclaration.methods.put(method, methodDeclaration); 97 return methodDeclaration.code; 98 } 99 100 /** 101 * @param flags any flags masked by {@link AccessFlags#FIELD_FLAGS}. 102 */ 103 public void declare(FieldId<?, ?> fieldId, int flags, Object staticValue) { 104 TypeDeclaration typeDeclaration = getTypeDeclaration(fieldId.declaringType); 105 if (typeDeclaration.fields.containsKey(fieldId)) { 106 throw new IllegalStateException("already declared: " + fieldId); 107 } 108 FieldDeclaration fieldDeclaration = new FieldDeclaration(fieldId, flags, staticValue); 109 typeDeclaration.fields.put(fieldId, fieldDeclaration); 110 } 111 112 /** 113 * Returns a .dex formatted file. 114 */ 115 public byte[] generate() { 116 DexOptions options = new DexOptions(); 117 options.targetApiLevel = DexFormat.API_NO_EXTENDED_OPCODES; 118 DexFile outputDex = new DexFile(options); 119 120 for (TypeDeclaration typeDeclaration : types.values()) { 121 outputDex.add(typeDeclaration.toClassDefItem()); 122 } 123 124 try { 125 return outputDex.toDex(null, false); 126 } catch (IOException e) { 127 throw new RuntimeException(e); 128 } 129 } 130 131 /** 132 * Loads the generated types into the current process. 133 * 134 * <p>All parameters are optional, you may pass {@code null} and suitable 135 * defaults will be used. 136 * 137 * <p>If you opt to provide your own output directories, take care to 138 * ensure that they are not world-readable, otherwise a malicious app will 139 * be able to inject code to run. A suitable parameter for these output 140 * directories would be something like this: 141 * {@code getApplicationContext().getDir("dx", Context.MODE_PRIVATE); } 142 * 143 * @param parent the parent ClassLoader to be used when loading 144 * our generated types 145 * @param dexOutputDir the destination directory wherein we will write 146 * emitted .dex files before they end up in the cache directory 147 * @param dexOptCacheDir where optimized .dex files are to be written 148 */ 149 public ClassLoader load(ClassLoader parent, File dexOutputDir, File dexOptCacheDir) 150 throws IOException { 151 byte[] dex = generate(); 152 153 /* 154 * This implementation currently dumps the dex to the filesystem. It 155 * jars the emitted .dex for the benefit of Gingerbread and earlier 156 * devices, which can't load .dex files directly. 157 * 158 * TODO: load the dex from memory where supported. 159 */ 160 File result = File.createTempFile("Generated", ".jar", dexOutputDir); 161 result.deleteOnExit(); 162 JarOutputStream jarOut = new JarOutputStream(new FileOutputStream(result)); 163 jarOut.putNextEntry(new JarEntry(DexFormat.DEX_IN_JAR_NAME)); 164 jarOut.write(dex); 165 jarOut.closeEntry(); 166 jarOut.close(); 167 try { 168 return (ClassLoader) Class.forName("dalvik.system.DexClassLoader") 169 .getConstructor(String.class, String.class, String.class, ClassLoader.class) 170 .newInstance(result.getPath(), dexOptCacheDir.getAbsolutePath(), null, parent); 171 } catch (ClassNotFoundException e) { 172 throw new UnsupportedOperationException("load() requires a Dalvik VM", e); 173 } catch (InvocationTargetException e) { 174 throw new RuntimeException(e.getCause()); 175 } catch (InstantiationException e) { 176 throw new AssertionError(); 177 } catch (NoSuchMethodException e) { 178 throw new AssertionError(); 179 } catch (IllegalAccessException e) { 180 throw new AssertionError(); 181 } 182 } 183 184 private static class TypeDeclaration { 185 private final Type<?> type; 186 187 /** declared state */ 188 private boolean declared; 189 private int flags; 190 private Type<?> supertype; 191 private String sourceFile; 192 private TypeList interfaces; 193 194 private final Map<FieldId, FieldDeclaration> fields 195 = new LinkedHashMap<FieldId, FieldDeclaration>(); 196 private final Map<MethodId, MethodDeclaration> methods 197 = new LinkedHashMap<MethodId, MethodDeclaration>(); 198 199 TypeDeclaration(Type<?> type) { 200 this.type = type; 201 } 202 203 ClassDefItem toClassDefItem() { 204 if (!declared) { 205 throw new IllegalStateException("Undeclared type " + type + " declares members: " 206 + fields.keySet() + " " + methods.keySet()); 207 } 208 209 DexOptions dexOptions = new DexOptions(); 210 dexOptions.targetApiLevel = DexFormat.API_NO_EXTENDED_OPCODES; 211 212 CstType thisType = type.constant; 213 214 ClassDefItem out = new ClassDefItem(thisType, flags, supertype.constant, 215 interfaces.ropTypes, new CstString(sourceFile)); 216 217 for (MethodDeclaration method : methods.values()) { 218 EncodedMethod encoded = method.toEncodedMethod(dexOptions); 219 if (method.isDirect()) { 220 out.addDirectMethod(encoded); 221 } else { 222 out.addVirtualMethod(encoded); 223 } 224 } 225 for (FieldDeclaration field : fields.values()) { 226 EncodedField encoded = field.toEncodedField(); 227 if (field.isStatic()) { 228 out.addStaticField(encoded, Constants.getConstant(field.staticValue)); 229 } else { 230 out.addInstanceField(encoded); 231 } 232 } 233 234 return out; 235 } 236 } 237 238 static class FieldDeclaration { 239 final FieldId<?, ?> fieldId; 240 private final int accessFlags; 241 private final Object staticValue; 242 243 FieldDeclaration(FieldId<?, ?> fieldId, int accessFlags, Object staticValue) { 244 if ((accessFlags & STATIC) == 0 && staticValue != null) { 245 throw new IllegalArgumentException("instance fields may not have a value"); 246 } 247 this.fieldId = fieldId; 248 this.accessFlags = accessFlags; 249 this.staticValue = staticValue; 250 } 251 252 EncodedField toEncodedField() { 253 return new EncodedField(fieldId.constant, accessFlags); 254 } 255 256 public boolean isStatic() { 257 return (accessFlags & STATIC) != 0; 258 } 259 } 260 261 static class MethodDeclaration { 262 final MethodId<?, ?> method; 263 private final int flags; 264 private final Code code; 265 266 public MethodDeclaration(MethodId<?, ?> method, int flags) { 267 this.method = method; 268 this.flags = flags; 269 this.code = new Code(this); 270 } 271 272 boolean isStatic() { 273 return (flags & STATIC) != 0; 274 } 275 276 boolean isDirect() { 277 return (flags & (STATIC | PRIVATE | ACC_CONSTRUCTOR)) != 0; 278 } 279 280 EncodedMethod toEncodedMethod(DexOptions dexOptions) { 281 RopMethod ropMethod = new RopMethod(code.toBasicBlocks(), 0); 282 LocalVariableInfo locals = null; 283 DalvCode dalvCode = RopTranslator.translate( 284 ropMethod, PositionList.NONE, locals, code.paramSize(), dexOptions); 285 return new EncodedMethod(method.constant, flags, dalvCode, StdTypeList.EMPTY); 286 } 287 } 288} 289