• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 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.xts.apimapper
18 
19 import com.android.xts.apimapper.adapter.AndroidApiInjectionSettings
20 import com.android.xts.apimapper.adapter.MethodCallHookingAdapter
21 import com.android.xts.apimapper.adapter.RuleInjectingAdapter
22 import com.android.xts.apimapper.adapter.shouldProcessClass
23 import com.android.xts.apimapper.asm.ClassNodes
24 import com.android.xts.apimapper.asm.InvalidJarFileException
25 import com.android.xts.apimapper.asm.loadClassStructure
26 import com.android.xts.apimapper.asm.zipEntryNameToClassName
27 import com.android.xts.apimapper.config.Configuration
28 import java.io.BufferedInputStream
29 import java.io.FileOutputStream
30 import java.util.zip.ZipEntry
31 import java.util.zip.ZipFile
32 import java.util.zip.ZipOutputStream
33 import org.objectweb.asm.ClassReader
34 import org.objectweb.asm.ClassVisitor
35 import org.objectweb.asm.ClassWriter
36 
37 const val CONFIG_FILE = "config.xml"
38 
39 /** Inject the code to record API calls. */
40 class ApiMapper(val args: Array<String>) {
41 
42     fun run() {
43         val apiMapperOption = ApiMapperOption(args)
44         apiMapperOption.validateOptions()
45         process(
46             apiMapperOption.getInJar(),
47             apiMapperOption.getOutJar(),
48             Configuration(CONFIG_FILE)
49         )
50     }
51 
52     private fun process(inJar: String, outJar: String, configuration: Configuration) {
53         val classNodes = loadClassStructure(inJar)
54         ZipFile(inJar).use { inZip ->
55             val inEntries = inZip.entries()
56             ZipOutputStream(FileOutputStream(outJar)).use { outZip ->
57                 while (inEntries.hasMoreElements()) {
58                     val entry = inEntries.nextElement()
59                     if (entry.name.endsWith(".dex")) {
60                         throw InvalidJarFileException("$inJar is not a jar file.")
61                     }
62                     val className = zipEntryNameToClassName(entry.name)
63                     if (className != null && shouldProcessClass(className, inJar, configuration)) {
64                         processSingleClass(inZip, entry, outZip, classNodes, configuration)
65                     } else {
66                         // Simply copy entries not need to be instrumented.
67                         copyZipEntry(inZip, entry, outZip)
68                     }
69                 }
70             }
71         }
72     }
73 
74     /** Copy a single ZIP entry to the output. */
75     private fun copyZipEntry(
76         inZip: ZipFile,
77         entry: ZipEntry,
78         outZip: ZipOutputStream,
79     ) {
80         BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
81             val outEntry = ZipEntry(entry.name)
82             outZip.putNextEntry(outEntry)
83             while (bis.available() > 0) {
84                 outZip.write(bis.read())
85             }
86             outZip.closeEntry()
87         }
88     }
89 
90     private fun processSingleClass(
91         inZip: ZipFile,
92         entry: ZipEntry,
93         outZip: ZipOutputStream,
94         classNodes: ClassNodes,
95         configuration: Configuration
96     ) {
97         val newEntry = ZipEntry(entry.name)
98         outZip.putNextEntry(newEntry)
99         BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
100             val cr = ClassReader(bis)
101             val flags = ClassWriter.COMPUTE_MAXS
102 
103             val cw = ClassWriter(flags)
104             var outVisitor: ClassVisitor = cw
105             outVisitor = RuleInjectingAdapter(outVisitor, classNodes)
106             outVisitor = MethodCallHookingAdapter(
107                 outVisitor,
108                 AndroidApiInjectionSettings(configuration),
109                 classNodes
110             )
111             cr.accept(outVisitor, ClassReader.EXPAND_FRAMES)
112             val data = cw.toByteArray()
113             outZip.write(data)
114         }
115         outZip.closeEntry()
116     }
117 }
118