1 /* 2 * Copyright (C) 2018 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 17 package com.android.cts.releaseparser; 18 19 import com.android.cts.releaseparser.ReleaseProto.*; 20 import com.google.protobuf.TextFormat; 21 22 import org.jf.dexlib2.DexFileFactory; 23 import org.jf.dexlib2.Opcodes; 24 import org.jf.dexlib2.ReferenceType; 25 import org.jf.dexlib2.ValueType; 26 import org.jf.dexlib2.dexbacked.DexBackedClassDef; 27 import org.jf.dexlib2.dexbacked.DexBackedDexFile; 28 import org.jf.dexlib2.dexbacked.DexBackedField; 29 import org.jf.dexlib2.dexbacked.DexBackedMethod; 30 import org.jf.dexlib2.dexbacked.reference.DexBackedFieldReference; 31 import org.jf.dexlib2.dexbacked.reference.DexBackedMethodReference; 32 import org.jf.dexlib2.dexbacked.reference.DexBackedTypeReference; 33 import org.jf.dexlib2.iface.Annotation; 34 import org.jf.dexlib2.iface.AnnotationElement; 35 import org.jf.dexlib2.iface.reference.*; 36 import org.jf.dexlib2.iface.value.*; 37 38 import java.io.File; 39 import java.io.FileOutputStream; 40 import java.io.IOException; 41 import java.nio.charset.Charset; 42 import java.util.ArrayList; 43 import java.util.HashMap; 44 import java.util.List; 45 import java.util.Set; 46 import java.util.logging.Level; 47 import java.util.logging.Logger; 48 import java.util.stream.Collectors; 49 50 public class DexParser extends FileParser { 51 private ApiPackage.Builder mExternalApiPackageBuilder; 52 private HashMap<String, ApiClass.Builder> mExternalApiClassBuilderMap; 53 private ApiPackage.Builder mInternalApiPackageBuilder; 54 private HashMap<String, ApiClass.Builder> mInternalApiClassBuilderMap; 55 private String mPackageName; 56 private boolean mParseInternalApi; 57 DexParser(File file)58 public DexParser(File file) { 59 super(file); 60 // default is the file name with out extenion 61 mPackageName = getFileName().split("\\.")[0]; 62 } 63 64 @Override getType()65 public Entry.EntryType getType() { 66 return Entry.EntryType.APK; 67 } 68 setPackageName(String name)69 public void setPackageName(String name) { 70 mPackageName = name; 71 } 72 setParseInternalApi(boolean parseInternalApi)73 public void setParseInternalApi(boolean parseInternalApi) { 74 mParseInternalApi = parseInternalApi; 75 } 76 getExternalApiPackage()77 public ApiPackage getExternalApiPackage() { 78 if (mExternalApiPackageBuilder == null) { 79 parse(); 80 } 81 return mExternalApiPackageBuilder.build(); 82 } 83 getInternalApiPackage()84 public ApiPackage getInternalApiPackage() { 85 if (mInternalApiPackageBuilder == null) { 86 parse(); 87 } 88 return mInternalApiPackageBuilder.build(); 89 } 90 parse()91 private void parse() { 92 mExternalApiPackageBuilder = ApiPackage.newBuilder(); 93 mExternalApiPackageBuilder.setName(mPackageName); 94 mExternalApiClassBuilderMap = new HashMap<String, ApiClass.Builder>(); 95 mInternalApiPackageBuilder = ApiPackage.newBuilder(); 96 mInternalApiPackageBuilder.setName(mPackageName); 97 mInternalApiClassBuilderMap = new HashMap<String, ApiClass.Builder>(); 98 DexBackedDexFile dexFile = null; 99 100 // Loads a Dex file 101 System.out.println("dexFile: " + getFile().getAbsoluteFile()); 102 try { 103 dexFile = DexFileFactory.loadDexFile(getFile().getAbsoluteFile(), Opcodes.getDefault()); 104 105 // Iterates through all clesses in the Dex file 106 for (DexBackedClassDef classDef : dexFile.getClasses()) { 107 // Still need to build the map to filter out internal classes later 108 ApiClass.Builder classBuilder = 109 ClassUtils.getApiClassBuilder( 110 mInternalApiClassBuilderMap, classDef.getType()); 111 if (mParseInternalApi) { 112 classBuilder.setAccessFlags(classDef.getAccessFlags()); 113 classBuilder.setSuperClass( 114 ClassUtils.getCanonicalName(classDef.getSuperclass())); 115 classBuilder.addAllInterfaces( 116 classDef.getInterfaces() 117 .stream() 118 .map(iType -> ClassUtils.getCanonicalName(iType)) 119 .collect(Collectors.toList())); 120 121 List<ApiAnnotation> annLst = getAnnotationList(classDef.getAnnotations()); 122 if (!annLst.isEmpty()) { 123 classBuilder.addAllAnnotations(annLst); 124 } 125 126 for (DexBackedField dxField : classDef.getFields()) { 127 ApiField.Builder fieldBuilder = ApiField.newBuilder(); 128 fieldBuilder.setName(dxField.getName()); 129 fieldBuilder.setType(ClassUtils.getCanonicalName(dxField.getType())); 130 fieldBuilder.setAccessFlags(dxField.getAccessFlags()); 131 annLst = getAnnotationList(dxField.getAnnotations()); 132 if (!annLst.isEmpty()) { 133 fieldBuilder.addAllAnnotations(annLst); 134 } 135 classBuilder.addFields(fieldBuilder.build()); 136 } 137 138 for (DexBackedMethod dxMethod : classDef.getMethods()) { 139 ApiMethod.Builder methodBuilder = ApiMethod.newBuilder(); 140 methodBuilder.setName(dxMethod.getName()); 141 methodBuilder.setAccessFlags(dxMethod.getAccessFlags()); 142 for (String parameter : dxMethod.getParameterTypes()) { 143 methodBuilder.addParameters(ClassUtils.getCanonicalName(parameter)); 144 } 145 methodBuilder.setReturnType( 146 ClassUtils.getCanonicalName(dxMethod.getReturnType())); 147 annLst = getAnnotationList(dxMethod.getAnnotations()); 148 if (!annLst.isEmpty()) { 149 methodBuilder.addAllAnnotations(annLst); 150 } 151 classBuilder.addMethods(methodBuilder.build()); 152 } 153 } 154 } 155 156 dexFile.getReferences(ReferenceType.FIELD) 157 .stream() 158 .map(f -> (DexBackedFieldReference) f) 159 .filter(f -> (!mInternalApiClassBuilderMap.containsKey(f.getDefiningClass()))) 160 .forEach(f -> processField(f)); 161 162 dexFile.getReferences(ReferenceType.METHOD) 163 .stream() 164 .map(m -> (DexBackedMethodReference) m) 165 .filter(m -> (!mInternalApiClassBuilderMap.containsKey(m.getDefiningClass()))) 166 .filter( 167 m -> 168 !(m.getDefiningClass().startsWith("[") 169 && m.getName().equals("clone"))) 170 .forEach(m -> processMethod(m)); 171 172 ClassUtils.addAllApiClasses(mExternalApiClassBuilderMap, mExternalApiPackageBuilder); 173 if (mParseInternalApi) { 174 ClassUtils.addAllApiClasses( 175 mInternalApiClassBuilderMap, mInternalApiPackageBuilder); 176 } 177 } catch (IOException | DexFileFactory.DexFileNotFoundException ex) { 178 String error = "Unable to load dex file: " + getFile().getAbsoluteFile(); 179 mExternalApiPackageBuilder.setError(error); 180 System.err.println(error); 181 ex.printStackTrace(); 182 } 183 } 184 getAnnotationList(Set<? extends Annotation> annotations)185 private List<ApiAnnotation> getAnnotationList(Set<? extends Annotation> annotations) { 186 List<ApiAnnotation> apiAnnotationList = new ArrayList<ApiAnnotation>(); 187 for (Annotation annotation : annotations) { 188 ApiAnnotation.Builder apiAnnotationBuilder = ApiAnnotation.newBuilder(); 189 apiAnnotationBuilder.setType(ClassUtils.getCanonicalName(annotation.getType())); 190 Set<? extends AnnotationElement> elements = annotation.getElements(); 191 for (AnnotationElement ele : elements) { 192 Element.Builder elementBuilder = Element.newBuilder(); 193 elementBuilder.setName(ele.getName()); 194 elementBuilder.setValue(getEncodedValueString(ele.getValue())); 195 apiAnnotationBuilder.addElements(elementBuilder.build()); 196 } 197 apiAnnotationList.add(apiAnnotationBuilder.build()); 198 } 199 return apiAnnotationList; 200 } 201 getEncodedValueString(EncodedValue encodedValue)202 private String getEncodedValueString(EncodedValue encodedValue) { 203 switch (encodedValue.getValueType()) { 204 case ValueType.BYTE: 205 return String.format("0x%X", ((ByteEncodedValue) encodedValue).getValue()); 206 case ValueType.SHORT: 207 return String.format("%d", ((ShortEncodedValue) encodedValue).getValue()); 208 case ValueType.CHAR: 209 return String.format("%c", ((CharEncodedValue) encodedValue).getValue()); 210 case ValueType.INT: 211 return String.format("%d", ((IntEncodedValue) encodedValue).getValue()); 212 case ValueType.LONG: 213 return String.format("%d", ((LongEncodedValue) encodedValue).getValue()); 214 case ValueType.FLOAT: 215 return String.format("%f", ((FloatEncodedValue) encodedValue).getValue()); 216 case ValueType.DOUBLE: 217 return String.format("%f", ((DoubleEncodedValue) encodedValue).getValue()); 218 case ValueType.STRING: 219 return ((StringEncodedValue) encodedValue).getValue(); 220 case ValueType.NULL: 221 return "null"; 222 case ValueType.BOOLEAN: 223 return Boolean.toString(((BooleanEncodedValue) encodedValue).getValue()); 224 case ValueType.TYPE: 225 return ClassUtils.getCanonicalName(((TypeEncodedValue) encodedValue).getValue()); 226 case ValueType.ARRAY: 227 ArrayList<String> lst = new ArrayList<String>(); 228 for (EncodedValue eValue : ((ArrayEncodedValue) encodedValue).getValue()) { 229 lst.add(getEncodedValueString(eValue)); 230 } 231 return String.join(",", lst); 232 case ValueType.FIELD: 233 return ((FieldReference) ((FieldEncodedValue) encodedValue).getValue()).getName(); 234 case ValueType.METHOD: 235 return ((MethodReference) ((MethodEncodedValue) encodedValue).getValue()).getName(); 236 case ValueType.ENUM: 237 return ((FieldReference) ((EnumEncodedValue) encodedValue).getValue()).getName(); 238 default: 239 getLogger() 240 .log( 241 Level.WARNING, 242 String.format( 243 "ToDo,Encoded Type,0x%X", encodedValue.getValueType())); 244 return String.format("Encoded Type:%x", encodedValue.getValueType()); 245 } 246 } 247 processField(DexBackedFieldReference f)248 private void processField(DexBackedFieldReference f) { 249 ApiField.Builder fieldBuilder = ApiField.newBuilder(); 250 fieldBuilder.setName(f.getName()); 251 fieldBuilder.setType(ClassUtils.getCanonicalName(f.getType())); 252 ApiClass.Builder classBuilder = 253 ClassUtils.getApiClassBuilder(mExternalApiClassBuilderMap, f.getDefiningClass()); 254 classBuilder.addFields(fieldBuilder.build()); 255 } 256 processMethod(DexBackedMethodReference m)257 private void processMethod(DexBackedMethodReference m) { 258 ApiMethod.Builder methodBuilder = ApiMethod.newBuilder(); 259 methodBuilder.setName(m.getName()); 260 for (String parameter : m.getParameterTypes()) { 261 methodBuilder.addParameters(ClassUtils.getCanonicalName(parameter)); 262 } 263 methodBuilder.setReturnType(ClassUtils.getCanonicalName(m.getReturnType())); 264 ApiClass.Builder classBuilder = 265 ClassUtils.getApiClassBuilder(mExternalApiClassBuilderMap, m.getDefiningClass()); 266 classBuilder.addMethods(methodBuilder.build()); 267 } 268 isInternal(DexBackedTypeReference t)269 private boolean isInternal(DexBackedTypeReference t) { 270 if (t.getType().length() == 1) { 271 // primitive class 272 return true; 273 } else if (t.getType().charAt(0) == ClassUtils.TYPE_ARRAY) { 274 return true; 275 } 276 return false; 277 } 278 getSignature(DexBackedTypeReference f)279 private String getSignature(DexBackedTypeReference f) { 280 return f.getType(); 281 } 282 getSignature(DexBackedMethod m)283 private String getSignature(DexBackedMethod m) { 284 return m.getDefiningClass() 285 + "." 286 + m.getName() 287 + "," 288 + String.join(",", m.getParameterTypes()) 289 + "," 290 + m.getReturnType(); 291 } 292 getSignature(DexBackedField f)293 private String getSignature(DexBackedField f) { 294 return ClassUtils.getCanonicalName(f.getDefiningClass()) 295 + "." 296 + f.getName() 297 + "." 298 + f.getType(); 299 } 300 getCanonicalName(DexBackedTypeReference f)301 private String getCanonicalName(DexBackedTypeReference f) { 302 return ClassUtils.getCanonicalName(f.getType()); 303 } 304 getCanonicalName(DexBackedFieldReference f)305 private String getCanonicalName(DexBackedFieldReference f) { 306 return ClassUtils.getCanonicalName(f.getDefiningClass()) 307 + "." 308 + f.getName() 309 + " : " 310 + ClassUtils.getCanonicalName(f.getType()); 311 } 312 getCanonicalName(DexBackedMethodReference m)313 private String getCanonicalName(DexBackedMethodReference m) { 314 return ClassUtils.getCanonicalName(m.getDefiningClass()) 315 + "." 316 + m.getName() 317 + " (" 318 + toParametersString(m.getParameterTypes()) 319 + ")" 320 + ClassUtils.getCanonicalName(m.getReturnType()); 321 } 322 toParametersString(List<String> pList)323 private String toParametersString(List<String> pList) { 324 return pList.stream() 325 .map(p -> ClassUtils.getCanonicalName(p)) 326 .collect(Collectors.joining(", ")); 327 } 328 329 private static final String USAGE_MESSAGE = 330 "Usage: java -jar releaseparser.jar " 331 + DexParser.class.getCanonicalName() 332 + " [-options <parameter>]...\n" 333 + " to prase APK file Dex data\n" 334 + "Options:\n" 335 + "\t-i PATH\t The file path of the file to be parsed.\n" 336 + "\t-pi \t Parses internal methods and fields too. Output will be large when parsing multiple files in a release.\n" 337 + "\t-of PATH\t The file path of the output file instead of printing to System.out.\n"; 338 main(String[] args)339 public static void main(String[] args) { 340 try { 341 ArgumentParser argParser = new ArgumentParser(args); 342 String fileName = argParser.getParameterElement("i", 0); 343 String outputFileName = argParser.getParameterElement("of", 0); 344 boolean parseInternalApi = argParser.containsOption("pi"); 345 346 String apkFileName = null; 347 File apkFile = new File(fileName); 348 DexParser aParser = new DexParser(apkFile); 349 aParser.setParseInternalApi(parseInternalApi); 350 351 if (outputFileName != null) { 352 FileOutputStream txtOutput = new FileOutputStream(outputFileName); 353 txtOutput.write( 354 TextFormat.printToString(aParser.getExternalApiPackage()) 355 .getBytes(Charset.forName("UTF-8"))); 356 if (parseInternalApi) { 357 txtOutput.write( 358 TextFormat.printToString(aParser.getInternalApiPackage()) 359 .getBytes(Charset.forName("UTF-8"))); 360 } 361 txtOutput.flush(); 362 txtOutput.close(); 363 } else { 364 System.out.println(TextFormat.printToString(aParser.getExternalApiPackage())); 365 if (parseInternalApi) { 366 System.out.println(TextFormat.printToString(aParser.getInternalApiPackage())); 367 } 368 } 369 } catch (Exception ex) { 370 System.out.println(USAGE_MESSAGE); 371 ex.printStackTrace(); 372 } 373 } 374 getLogger()375 private static Logger getLogger() { 376 return Logger.getLogger(DexParser.class.getSimpleName()); 377 } 378 } 379