Initial sdkparcelables

Adds a tool that can convert an SDK stubs jar into a framework.aidl
file by parsing the jar with ASM to find classes that implement
android.os.Parcelable directly or indirectly.

Bug: 70046217
Test: java -cp out/host/linux-x86/framework/sdk_parcelables_test.jar org.junit.runner.JUnitCore com.android.sdk_parcelables.ParcelableDetectorTest
Change-Id: Idc804896b8860352633a85168748af1b08777205
This commit is contained in:
Colin Cross
2017-12-15 11:07:53 -08:00
parent 4e54d6092b
commit 149008d892
6 changed files with 216 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
java_binary_host {
name: "sdkparcelables",
manifest: "manifest.txt",
srcs: [
"src/**/*.kt",
],
static_libs: [
"asm-6.0",
],
}
java_library_host {
name: "sdkparcelables_test",
manifest: "manifest.txt",
srcs: [
"tests/**/*.kt",
],
static_libs: [
"sdkparcelables",
"junit",
],
}

View File

@@ -0,0 +1 @@
Main-class: com.android.sdkparcelables.MainKt

View File

@@ -0,0 +1,28 @@
package com.android.sdkparcelables
import org.objectweb.asm.ClassVisitor
import java.util.*
data class Ancestors(val superName: String?, val interfaces: List<String>?)
/** A class that implements an ASM ClassVisitor that collects super class and
* implemented interfaces for each class that it visits.
*/
class AncestorCollector(api: Int, dest: ClassVisitor?) : ClassVisitor(api, dest) {
private val _ancestors = LinkedHashMap<String, Ancestors>()
val ancestors: Map<String, Ancestors>
get() = _ancestors
override fun visit(version: Int, access: Int, name: String?, signature: String?,
superName: String?, interfaces: Array<out String>?) {
name!!
val old = _ancestors.put(name, Ancestors(superName, interfaces?.toList()))
if (old != null) {
throw RuntimeException("class $name already found")
}
super.visit(version, access, name, signature, superName, interfaces)
}
}

View File

@@ -0,0 +1,56 @@
package com.android.sdkparcelables
import org.objectweb.asm.ClassReader
import org.objectweb.asm.Opcodes
import java.io.File
import java.io.IOException
import java.util.zip.ZipFile
fun main(args: Array<String>) {
if (args.size != 2) {
usage()
}
val zipFileName = args[0]
val aidlFileName = args[1]
val zipFile: ZipFile
try {
zipFile = ZipFile(zipFileName)
} catch (e: IOException) {
System.err.println("error reading input jar: ${e.message}")
kotlin.system.exitProcess(2)
}
val ancestorCollector = AncestorCollector(Opcodes.ASM6, null)
for (entry in zipFile.entries()) {
if (entry.name.endsWith(".class")) {
val reader = ClassReader(zipFile.getInputStream(entry))
reader.accept(ancestorCollector,
ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES)
}
}
val parcelables = ParcelableDetector.ancestorsToParcelables(ancestorCollector.ancestors)
try {
val outFile = File(aidlFileName)
val outWriter = outFile.bufferedWriter()
for (parcelable in parcelables) {
outWriter.write("parcelable ")
outWriter.write(parcelable.replace('/', '.').replace('$', '.'))
outWriter.write(";\n")
}
outWriter.flush()
} catch (e: IOException) {
System.err.println("error writing output aidl: ${e.message}")
kotlin.system.exitProcess(2)
}
}
fun usage() {
System.err.println("Usage: <input jar> <output aidl>")
kotlin.system.exitProcess(1)
}

View File

@@ -0,0 +1,52 @@
package com.android.sdkparcelables
/** A class that uses an ancestor map to find all classes that
* implement android.os.Parcelable, including indirectly through
* super classes or super interfaces.
*/
class ParcelableDetector {
companion object {
fun ancestorsToParcelables(ancestors: Map<String, Ancestors>): List<String> {
val impl = Impl(ancestors)
impl.build()
return impl.parcelables
}
}
private class Impl(val ancestors: Map<String, Ancestors>) {
val isParcelableCache = HashMap<String, Boolean>()
val parcelables = ArrayList<String>()
fun build() {
val classList = ancestors.keys
classList.filterTo(parcelables, this::isParcelable)
parcelables.sort()
}
private fun isParcelable(c: String?): Boolean {
if (c == null) {
return false
}
if (c == "android/os/Parcelable") {
return true
}
val old = isParcelableCache[c]
if (old != null) {
return old
}
val cAncestors = ancestors[c] ?:
throw RuntimeException("class $c missing ancestor information")
val seq = (cAncestors.interfaces?.asSequence() ?: emptySequence()) +
cAncestors.superName
val ancestorIsParcelable = seq.any(this::isParcelable)
isParcelableCache[c] = ancestorIsParcelable
return ancestorIsParcelable
}
}
}

View File

@@ -0,0 +1,57 @@
package com.android.sdkparcelables
import junit.framework.TestCase.assertEquals
import org.junit.Test
class ParcelableDetectorTest {
@Test
fun `detect implements`() {
val ancestorMap = mapOf(
testAncestors("android/test/Parcelable",null, "android/os/Parcelable"),
testAncestors("android/os/Parcelable", null))
val parcelables = ParcelableDetector.ancestorsToParcelables(ancestorMap)
assertEquals(parcelables, listOf("android/os/Parcelable", "android/test/Parcelable"))
}
@Test
fun `detect implements in reverse order`() {
val ancestorMap = mapOf(
testAncestors("android/os/Parcelable", null),
testAncestors("android/test/Parcelable",null, "android/os/Parcelable"))
val parcelables = ParcelableDetector.ancestorsToParcelables(ancestorMap)
assertEquals(parcelables, listOf("android/os/Parcelable", "android/test/Parcelable"))
}
@Test
fun `detect super implements`() {
val ancestorMap = mapOf(
testAncestors("android/test/SuperParcelable",null, "android/os/Parcelable"),
testAncestors("android/test/Parcelable","android/test/SuperParcelable"),
testAncestors("android/os/Parcelable", null))
val parcelables = ParcelableDetector.ancestorsToParcelables(ancestorMap)
assertEquals(parcelables, listOf("android/os/Parcelable", "android/test/Parcelable", "android/test/SuperParcelable"))
}
@Test
fun `detect super interface`() {
val ancestorMap = mapOf(
testAncestors("android/test/IParcelable",null, "android/os/Parcelable"),
testAncestors("android/test/Parcelable",null, "android/test/IParcelable"),
testAncestors("android/os/Parcelable", null))
val parcelables = ParcelableDetector.ancestorsToParcelables(ancestorMap)
assertEquals(parcelables, listOf("android/os/Parcelable", "android/test/IParcelable", "android/test/Parcelable"))
}
}
private fun testAncestors(name: String, superName: String?, vararg interfaces: String): Pair<String, Ancestors> {
return Pair(name, Ancestors(superName, interfaces.toList()))
}