1 /*
2  * Copyright (C) 2023 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.server.sdksandbox.verifier;
18 
19 import com.android.server.sdksandbox.verifier.SerialDexLoader.DexLoadResult;
20 import com.android.tools.smali.dexlib2.DexFileFactory;
21 import com.android.tools.smali.dexlib2.DexFileFactory.DexFileNotFoundException;
22 import com.android.tools.smali.dexlib2.Opcodes;
23 import com.android.tools.smali.dexlib2.dexbacked.DexBackedDexFile;
24 import com.android.tools.smali.dexlib2.dexbacked.reference.DexBackedMethodReference;
25 import com.android.tools.smali.dexlib2.iface.MultiDexContainer;
26 
27 import java.io.File;
28 import java.io.IOException;
29 import java.util.ArrayList;
30 import java.util.Enumeration;
31 import java.util.HashMap;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.zip.ZipEntry;
35 import java.util.zip.ZipFile;
36 
37 /**
38  * DEX parser for SDK verification
39  *
40  * @hide
41  */
42 public class DexParserImpl implements DexParser {
43 
44     @Override
getDexFilePaths(File apkPathFile)45     public Map<File, List<String>> getDexFilePaths(File apkPathFile) throws IOException {
46         ArrayList<File> apkList = new ArrayList<>();
47 
48         // If multi-apk directory, find a base apk and zero or more split apks
49         if (apkPathFile.isDirectory()) {
50             for (File apkFile : apkPathFile.listFiles()) {
51                 if (apkFile.isFile() && apkFile.getName().endsWith(".apk")) {
52                     apkList.add(apkFile);
53                 }
54             }
55         } else {
56             apkList.add(apkPathFile);
57         }
58 
59         HashMap<File, List<String>> dexLists = new HashMap<>();
60 
61         for (File apk : apkList) {
62             try (ZipFile apkZipFile = new ZipFile(apk)) {
63                 Enumeration<? extends ZipEntry> entriesEnumeration = apkZipFile.entries();
64                 ArrayList<String> dexEntries = new ArrayList<>();
65 
66                 while (entriesEnumeration.hasMoreElements()) {
67                     String entryName = entriesEnumeration.nextElement().getName();
68                     if (entryName.endsWith(".dex")) {
69                         dexEntries.add(entryName);
70                     }
71                 }
72                 dexLists.put(apk, dexEntries);
73             } catch (IOException ex) {
74                 throw new IOException(apk.getName() + " is not a valid DEX container file.", ex);
75             }
76         }
77 
78         return (Map<File, List<String>>) dexLists;
79     }
80 
81     @Override
loadDexSymbols(File apkFile, String dexName, DexLoadResult dexLoadResult)82     public void loadDexSymbols(File apkFile, String dexName, DexLoadResult dexLoadResult)
83             throws IOException {
84         MultiDexContainer.DexEntry<? extends DexBackedDexFile> dexEntry;
85         try {
86             dexEntry =
87                     DexFileFactory.loadDexEntry(
88                             apkFile, dexName, /* exactMatch */ true, Opcodes.getDefault());
89         } catch (DexFileNotFoundException e) {
90             throw new IOException(e);
91         }
92         dexLoadResult.clear();
93 
94         DexBackedDexFile dexFile = dexEntry.getDexFile();
95 
96         for (DexBackedMethodReference method : dexFile.getMethodSection()) {
97             String methodString = method.getDefiningClass() + method.getName() + ";";
98             for (String param : method.getParameterTypes()) {
99                 methodString = methodString + param;
100             }
101             methodString = methodString + method.getReturnType();
102             dexLoadResult.addReferencedMethod(methodString);
103         }
104     }
105 }
106