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