• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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