Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Modifying Classes with Transform API

Modifying Classes with Transform API

Transform API is a part of the Android Gradle Plugin and provides hooks to manipulate the compiled class during the build time before classes are given for Dexing.
It simplifies injecting custom class manipulation without having to deal with Gradle tasks and offers more flexibility on what is manipulated. Some of the projects using the Transform API are ProGuard, MultiDex and RetroLambda. People who are attending the talk will learn how the Android Gradle Plugin works and how they can use the Transform API to manipulate java class files.

FarmaanElahi

January 11, 2020
Tweet

Other Decks in Programming

Transcript

  1. Is it same as Annotation Processor ? • Annotation Processor

    • Your code runs at compile time • Transform API • Your code runs after compilation but before dexing
  2. Is it same as Annotation Processor ? • Annotation Processor

    • Your code runs at compile time • Emits Java or Kotlin Source which is eventually compiled to bytecode • Transform API • Your code runs after compilation but before dexing
  3. Is it same as Annotation Processor ? • Annotation Processor

    • Your code runs at compile time • Emits Java or Kotlin Source which is eventually compiled to bytecode • Transform API • Your code runs after compilation but before dexing • Emits bytecode
  4. What can I build from it ? • Incredibly powerful

    API; you can modify any class internals • Helps achieves Aspect and Meta Oriented Programming Constructs
  5. When NOT to use this? • If your problem can

    be solved with an Annotation Processor • If you don’t understand bytecode and JVM internals
  6. Plugin Transformer Class Reader Class Visitor Class Writer • Gradle

    Plugin • Provides entry to build.gradle script • Allows configuration via Gradle Extension Architecture
  7. Plugin Transformer Class Reader Class Visitor Class Writer • Defines

    the scope of transformation • Project • Modules • External Libraries • Defines the input to this transformer (Can be classes or java resource) • Transformation Architecture
  8. Plugin Transformer Class Reader Class Visitor Class Writer • This

    is the beginning of the transforming a class • Helps to read the class file Architecture
  9. Plugin Transformer Class Reader Class Visitor Class Writer • Provides

    method to transform the class after reading the raw files • Provides various visit* method to transform different components • Requires a strict order in which this methods should be called to generate correct output. Architecture
  10. • We’ll build a transformation which that traces method call

    Let’s build our own fun prime(n: Int){ println("--->prime(n=$n)") val startTime = System.currentTimeMillis() val result = primeNumberSequence.take(n).last() val timeToRun = System.currentTimeMillis() - startTime println("<--- prime[ran in $timeToRun] ms") return result } fun prime(n:Int) = primeSequence.take(n).last()
  11. plugin/build.gradle apply plugin: 'java-library' apply plugin: 'java-gradle-plugin' gradlePlugin { plugins

    { logger { id = 'com.logger.plugin' -/ apply plugin :"com.logger.plugin" implementationClass = 'com.logger.plugin.LoggerPlugin' } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "com.android.tools.build:gradle-api:3.5.0" implementation "com.android.tools.build:gradle:3.5.0" }
  12. gradle-plugin/build.gradle apply plugin: 'java-library' apply plugin: 'java-gradle-plugin' gradlePlugin { plugins

    { logger { id = 'com.logger.plugin' -/ apply plugin :"com.logger.plugin" implementationClass = 'com.logger.plugin.LoggerPlugin' } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "com.android.tools.build:gradle-api:3.5.0" implementation "com.android.tools.build:gradle:3.5.0" }
  13. class LoggerPlugin : Plugin<Project> { override fun apply(project: Project) {

    val androidExtension = project.extensions.findByName("android") as? AppExtension androidExtension-.registerTransform(DebugTransformer()) } } plugin/src/main/kotlin/com/logger/plugin/LoggerPlugin.kt
  14. class LoggerPlugin : Plugin<Project> { override fun apply(project: Project) {

    val androidExtension = project.extensions.findByName("android") as? AppExtension androidExtension-.registerTransform(DebugTransformer()) } } plugin/src/main/kotlin/com/logger/plugin/LoggerPlugin.kt
  15. class DebugTransformer : Transform() { override fun getName() = "DebugLogger"

    override fun getInputTypes() = setOf(DefaultContentType.CLASSES) override fun isIncremental() = false override fun getScopes() = mutableSetOf(QualifiedContent.Scope.PROJECT) override fun transform(transformInvocation: TransformInvocation?) } plugin/src/main/kotlin/com/logger/plugin/DebugTransformer.kt
  16. class DebugTransformer : Transform() { override fun getName() = "DebugLogger"

    override fun getInputTypes() = setOf(DefaultContentType.CLASSES) override fun isIncremental() = false override fun getScopes() = mutableSetOf(QualifiedContent.Scope.PROJECT) override fun transform(transformInvocation: TransformInvocation?) } plugin/src/main/kotlin/com/logger/plugin/DebugTransformer.kt
  17. class DebugTransformer : Transform() { override fun getName() = "DebugLogger"

    override fun getInputTypes() = setOf(DefaultContentType.CLASSES) override fun isIncremental() = false override fun getScopes() = mutableSetOf(QualifiedContent.Scope.PROJECT) override fun transform(transformInvocation: TransformInvocation?) } plugin/src/main/kotlin/com/logger/plugin/DebugTransformer.kt
  18. class DebugTransformer : Transform() { override fun getName() = "DebugLogger"

    override fun getInputTypes() = setOf(DefaultContentType.CLASSES) override fun isIncremental() = false override fun getScopes() = mutableSetOf(QualifiedContent.Scope.PROJECT) override fun transform(transformInvocation: TransformInvocation?) } plugin/src/main/kotlin/com/logger/plugin/DebugTransformer.kt
  19. class DebugTransformer : Transform() { override fun getName() = "DebugLogger"

    override fun getInputTypes() = setOf(DefaultContentType.CLASSES) override fun isIncremental() = false override fun getScopes() = mutableSetOf(QualifiedContent.Scope.PROJECT) override fun transform(transformInvocation: TransformInvocation?) } plugin/src/main/kotlin/com/logger/plugin/DebugTransformer.kt
  20. override fun transform(transformInvocation: TransformInvocation) { val outputProvider = transformInvocation.outputProvider inputs.flatMap

    { it.directoryInputs }.forEach { val destinationDirectory = outputProvider.getContentLocation( it.name, it.contentTypes, it.scopes, Format.DIRECTORY ) val allInputFiles = loadFileRecursively(it.file) } } plugin/src/main/kotlin/com/logger/plugin/DebugTransformer.kt
  21. override fun transform(transformInvocation: TransformInvocation) { val outputProvider = transformInvocation.outputProvider inputs.flatMap

    { it.directoryInputs }.forEach { val destinationDirectory = outputProvider.getContentLocation( it.name, it.contentTypes, it.scopes, Format.DIRECTORY ) val allInputFiles = loadFileRecursively(it.file) } } plugin/src/main/kotlin/com/logger/plugin/DebugTransformer.kt
  22. inputs.flatMap { it.directoryInputs }.forEach { --. val allInputFiles = loadFileRecursively(it.file)

    val inputDirPath = it.file.absolutePath val outputDirPath = destinationDirectory.absolutePath allInputFiles.forEach { inputFile -> val inputClassFilePath = inputFile.absolutePath val outputClassFile = File(inputClassFilePath.replace(inputDirPath, outputDirPath)) tranformClassFile(inputClassFile, outputClassFile) } } plugin/src/main/kotlin/com/logger/plugin/DebugTransformer.kt
  23. fun transformClass(inputFile: File, outputFile: File) { val classReader = ClassReader(inputFile.readBytes())

    val classWriter = ClassWriter(ASM5) val classVisitor = CustomClassVisitor(classWriter) classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES) outputFile.writeBytes(classWriter.toByteArray()) } plugin/src/main/kotlin/com/logger/plugin/DebugTransformer.kt
  24. fun transformClass(inputFile: File, outputFile: File) { val classReader = ClassReader(inputFile.readBytes())

    val classWriter = ClassWriter(ASM5) val classVisitor = CustomClassVisitor(classWriter) classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES) outputFile.writeBytes(classWriter.toByteArray()) } plugin/src/main/kotlin/com/logger/plugin/DebugTransformer.kt
  25. fun transformClass(inputFile: File, outputFile: File) { val classReader = ClassReader(inputFile.readBytes())

    val classWriter = ClassWriter(ASM5) val classVisitor = CustomClassVisitor(classWriter) classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES) outputFile.writeBytes(classWriter.toByteArray()) } plugin/src/main/kotlin/com/logger/plugin/DebugTransformer.kt
  26. fun transformClass(inputFile: File, outputFile: File) { val classReader = ClassReader(inputFile.readBytes())

    val classWriter = ClassWriter(ASM5) val classVisitor = CustomClassVisitor(classWriter) classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES) outputFile.writeBytes(classWriter.toByteArray()) } plugin/src/main/kotlin/com/logger/plugin/DebugTransformer.kt
  27. fun transformClass(inputFile: File, outputFile: File) { val classReader = ClassReader(inputFile.readBytes())

    val classWriter = ClassWriter(ASM5) val classVisitor = CustomClassVisitor(classWriter) classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES) outputFile.writeBytes(classWriter.toByteArray()) } plugin/src/main/kotlin/com/logger/plugin/DebugTransformer.kt
  28. • You write bytecode • JVM is stack machine ◦

    One Stack for each thread on which method operates ◦ You can store and load variable from the Local Variable Array ◦ There is a constant pool which store references to class, method and constant values What now?
  29. What does bytecode look like? fun printSimpleSum() { val sum

    = v1() + v2() println("sum of values was $sum") } INVOKESTATIC myapp/MainActivity.v1 ()I INVOKESTATIC myapp/MainActivity.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V
  30. fun printSimpleSum() { val sum = v1() + v2() println("sum

    of values was $sum") } INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V Return value of v1 What does bytecode look like?
  31. fun printSimpleSum() { val sum = v1() + v2() println("sum

    of values was $sum") } INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V Return value of v1 Return value of v2 What does bytecode look like?
  32. fun printSimpleSum() { val sum = v1() + v2() println("sum

    of values was $sum") } INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V Return value of v1 Return value of v2 What does bytecode look like?
  33. fun printSimpleSum() { val sum = v1() + v2() println("sum

    of values was $sum") } INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V v1() + v2() What does bytecode look like?
  34. fun printSimpleSum() { val sum = v1() + v2() println("sum

    of values was $sum") } INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V v1() + v2() What does bytecode look like?
  35. fun printSimpleSum() { val sum = v1() + v2() println("sum

    of values was $sum") } INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V Printstream Ref What does bytecode look like?
  36. fun printSimpleSum() { val sum = v1() + v2() println("sum

    of values was $sum") } INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V Printstream Ref StringBuilder What does bytecode look like?
  37. fun printSimpleSum() { val sum = v1() + v2() println("sum

    of values was $sum") } INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V Printstream Ref StringBuilder StringBuilder What does bytecode look like?
  38. fun printSimpleSum() { val sum = v1() + v2() println("sum

    of values was $sum") } INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V Printstream Ref StringBuilder StringBuilder What does bytecode look like?
  39. fun printSimpleSum() { val sum = v1() + v2() println("sum

    of values was $sum") } INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V Printstream Ref StringBuilder “Sum of values was” What does bytecode look like?
  40. fun printSimpleSum() { val sum = v1() + v2() println("sum

    of values was $sum") } INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V Printstream Ref StringBuilder “Sum of values was” What does bytecode look like?
  41. fun printSimpleSum() { val sum = v1() + v2() println("sum

    of values was $sum") } INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V Printstream Ref StringBuilder What does bytecode look like?
  42. fun printSimpleSum() { val sum = v1() + v2() println("sum

    of values was $sum") } INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V Printstream Ref StringBuilder Value in variable sum What does bytecode look like?
  43. fun printSimpleSum() { val sum = v1() + v2() println("sum

    of values was $sum") } INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V Printstream Ref StringBuilder Value in variable sum What does bytecode look like?
  44. fun printSimpleSum() { val sum = v1() + v2() println("sum

    of values was $sum") } INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V Printstream Ref StringBuilder What does bytecode look like?
  45. fun printSimpleSum() { val sum = v1() + v2() println("sum

    of values was $sum") } INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V Printstream Ref StringBuilder What does bytecode look like?
  46. What does bytecode look like? fun printSimpleSum() { val sum

    = v1() + v2() println("sum of values was $sum") } INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V Printstream Ref String
  47. What does bytecode look like? fun printSimpleSum() { val sum

    = v1() + v2() println("sum of values was $sum") } INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V Printstream Ref String
  48. Remember the goal fun prime(n: Int){ println("--->prime(n=$n)") val startTime =

    System.currentTimeMillis() val result = primeNumberSequence.take(n).last() val timeToRun = System.currentTimeMillis() - startTime println("<--- prime[ran in $timeToRun] ms") return result } fun prime(n:Int) = primeSequence.take(n).last()
  49. class CustomClassVisitor(private val classWriter: ClassWriter) : ClassVisitor(ASM5) { override fun

    visitMethod( access: Int, name: String?, desc: String?, signature: String?, exceptions: Array<out String>? ): MethodVisitor { super.visitMethod(access, name, desc, signature, exceptions) TODO("Implement custom method visitor") } } plugin/src/main/kotlin/com/logger/plugin/CustomClassVisitor.kt
  50. object : MethodVisitor(Opcodes.ASM5, mv) { override fun visitCode() { super.visitCode()

    InstructionAdapter(this).apply { TODO("on method entry") } } override fun visitInsn(opcode: Int) { when (opcode) { RETURN, ARETURN, IRETURN -> { InstructionAdapter(this).apply { TODO("on method exit") } } } super.visitInsn(opcode) } } plugin/src/main/kotlin/com/logger/plugin/CustomClassVisitor.kt
  51. object : MethodVisitor(Opcodes.ASM5, mv) { override fun visitCode() { super.visitCode()

    InstructionAdapter(this).apply { TODO("on method entry") } } override fun visitInsn(opcode: Int) { when (opcode) { RETURN, ARETURN, IRETURN -> { InstructionAdapter(this).apply { TODO("on method exit") } } } super.visitInsn(opcode) } } plugin/src/main/kotlin/com/logger/plugin/CustomClassVisitor.kt
  52. InstructionAdapter(this).apply { getstatic("j/l/System","out","Ljava/io/PrintStream;") anew(Type.getObjectType("j/l/StringBuilder")) dup() invokespecial("j/l/StringBuilder","<init>","()V") visitLdcInsn("---> $name(") invokevirtual("j/l/StringBuilder","append", "(Lj/l/Object;)Lj/l/StringBuilder;")

    function.valueParamters.forEachIndexed { i, param -> visitLdcInsn(" ${param.name}=") invokevirtual("j/l/SB", "append", "(Lj/l/String;)L/j/SB;") visitVarInsn(ALOAD,i + 1) invokevirtual("j/l/SB", "append", "(Lj/l/String;)L/j/SB;") } } Printstream Ref StringBuilder Ref plugin/src/main/kotlin/com/logger/plugin/CustomClassVisitor.kt
  53. InstructionAdapter(this).apply { getstatic("j/l/System","out","Ljava/io/PrintStream;") anew(Type.getObjectType("j/l/StringBuilder")) dup() invokespecial("j/l/StringBuilder","<init>","()V") visitLdcInsn("---> $name(") invokevirtual("j/l/StringBuilder","append", "(Lj/l/Object;)Lj/l/StringBuilder;")

    function.valueParamters.forEachIndexed { i, param -> visitLdcInsn(" ${paran.name}=") invokevirtual("j/l/SB", "append", "(Lj/l/String;)L/j/SB;") visitVarInsn(ALOAD,i + 1) invokevirtual("j/l/SB", "append", "(Lj/l/String;)L/j/SB;") } } Printstream Ref StringBuilder Ref “ n=” plugin/src/main/kotlin/com/logger/plugin/CustomClassVisitor.kt
  54. InstructionAdapter(this).apply { getstatic("j/l/System","out","Ljava/io/PrintStream;") anew(Type.getObjectType("j/l/StringBuilder")) dup() invokespecial("j/l/StringBuilder","<init>","()V") visitLdcInsn("---> $name(") invokevirtual("j/l/StringBuilder","append", "(Lj/l/Object;)Lj/l/StringBuilder;")

    function.valueParamters.forEachIndexed { i, param -> visitLdcInsn(" ${paran.name}=") invokevirtual("j/l/SB", "append", "(Lj/l/String;)L/j/SB;") visitVarInsn(ALOAD,i + 1) invokevirtual("j/l/SB", "append", "(Lj/l/String;)L/j/SB;") } } Printstream Ref plugin/src/main/kotlin/com/logger/plugin/CustomClassVisitor.kt StringBuilder Ref “ n=”
  55. InstructionAdapter(this).apply { getstatic("j/l/System","out","Ljava/io/PrintStream;") anew(Type.getObjectType("j/l/StringBuilder")) dup() invokespecial("j/l/StringBuilder","<init>","()V") visitLdcInsn("---> $name(") invokevirtual("j/l/StringBuilder","append", "(Lj/l/Object;)Lj/l/StringBuilder;")

    function.valueParamters.forEachIndexed { i, param -> visitLdcInsn(" ${paran.name}=") invokevirtual("j/l/SB", "append", "(Lj/l/String;)L/j/SB;") visitVarInsn(ALOAD,i + 1) invokevirtual("j/l/SB", "append", "(Lj/l/String;)L/j/SB;") } } Printstream Ref StringBuilder Ref plugin/src/main/kotlin/com/logger/plugin/CustomClassVisitor.kt
  56. InstructionAdapter(this).apply { getstatic("j/l/System","out","Ljava/io/PrintStream;") anew(Type.getObjectType("j/l/StringBuilder")) dup() invokespecial("j/l/StringBuilder","<init>","()V") visitLdcInsn("---> $name(") invokevirtual("j/l/StringBuilder","append", "(Lj/l/Object;)Lj/l/StringBuilder;")

    function.valueParamters.forEachIndexed { i, param -> visitLdcInsn(" ${paran.name}=") invokevirtual("j/l/SB", "append", "(Lj/l/String;)L/j/SB;") visitVarInsn(ALOAD,i + 1) invokevirtual("j/l/SB", "append", "(Lj/l/String;)L/j/SB;") } } Printstream Ref StringBuilder Ref Value of n plugin/src/main/kotlin/com/logger/plugin/CustomClassVisitor.kt
  57. InstructionAdapter(this).apply { getstatic("j/l/System","out","Ljava/io/PrintStream;") anew(Type.getObjectType("j/l/StringBuilder")) dup() invokespecial("j/l/StringBuilder","<init>","()V") visitLdcInsn("---> $name(") invokevirtual("j/l/StringBuilder","append", "(Lj/l/Object;)Lj/l/StringBuilder;")

    function.valueParamters.forEachIndexed { i, param -> visitLdcInsn(" ${paran.name}=") invokevirtual("j/l/SB", "append", "(Lj/l/String;)L/j/SB;") visitVarInsn(ALOAD,i + 1) invokevirtual("j/l/SB", "append", "(Lj/l/String;)L/j/SB;") } } Printstream Ref plugin/src/main/kotlin/com/logger/plugin/CustomClassVisitor.kt StringBuilder Ref Value of n
  58. InstructionAdapter(this).apply { getstatic("j/l/System","out","Ljava/io/PrintStream;") anew(Type.getObjectType("j/l/StringBuilder")) dup() invokespecial("j/l/StringBuilder","<init>","()V") visitLdcInsn("---> $name(") invokevirtual("j/l/StringBuilder","append", "(Lj/l/Object;)Lj/l/StringBuilder;")

    function.valueParamters.forEachIndexed { i, param -> visitLdcInsn(" ${paran.name}=") invokevirtual("j/l/SB", "append", "(Lj/l/String;)L/j/SB;") visitVarInsn(ALOAD,i + 1) invokevirtual("j/l/SB", "append", "(Lj/l/String;)L/j/SB;") } } Printstream Ref StringBuilder Ref plugin/src/main/kotlin/com/logger/plugin/CustomClassVisitor.kt
  59. InstructionAdapter(this).apply { getstatic("j/l/System","out","Ljava/io/PrintStream;") anew(Type.getObjectType("j/l/StringBuilder")) dup() invokespecial("j/l/StringBuilder","<init>","()V") visitLdcInsn("---> $name(") invokevirtual("j/l/StringBuilder","append", "(Lj/l/Object;)Lj/l/StringBuilder;")

    function.valueParamters.forEachIndexed { i, param -> visitLdcInsn(" ${paran.name}=") invokevirtual("j/l/SB", "append", "(Lj/l/String;)L/j/SB;") visitVarInsn(ALOAD,i + 1) invokevirtual("j/l/SB", "append", "(Lj/l/String;)L/j/SB;") } invokevirtual("j/l/StringBuilder","toString","(L/j/l/String;)V") } Printstream Ref plugin/src/main/kotlin/com/logger/plugin/CustomClassVisitor.kt StringBuilder Ref
  60. InstructionAdapter(this).apply { getstatic("j/l/System","out","Ljava/io/PrintStream;") anew(Type.getObjectType("j/l/StringBuilder")) dup() invokespecial("j/l/StringBuilder","<init>","()V") visitLdcInsn("---> $name(") invokevirtual("j/l/StringBuilder","append", "(Lj/l/Object;)Lj/l/StringBuilder;")

    function.valueParamters.forEachIndexed { i, param -> visitLdcInsn(" ${paran.name}=") invokevirtual("j/l/SB", "append", "(Lj/l/String;)L/j/SB;") visitVarInsn(ALOAD,i + 1) invokevirtual("j/l/SB", "append", "(Lj/l/String;)L/j/SB;") } invokevirtual("j/l/StringBuilder","toString","(L/j/l/String;)V") } Printstream Ref String plugin/src/main/kotlin/com/logger/plugin/CustomClassVisitor.kt
  61. InstructionAdapter(this).apply { getstatic("j/l/System","out","Ljava/io/PrintStream;") anew(Type.getObjectType("j/l/StringBuilder")) dup() invokespecial("j/l/StringBuilder","<init>","()V") visitLdcInsn("---> $name(") invokevirtual("j/l/StringBuilder","append", "(Lj/l/Object;)Lj/l/StringBuilder;")

    function.valueParamters.forEachIndexed { i, param -> visitLdcInsn(" ${paran.name}=") invokevirtual("j/l/SB", "append", "(Lj/l/String;)L/j/SB;") visitVarInsn(ALOAD,i + 1) invokevirtual("j/l/SB", "append", "(Lj/l/String;)L/j/SB;") } invokevirtual("j/l/StringBuilder","toString","(L/j/l/String;)V") invokevirtual("j/io/PrintStream","println","(L/j/l/String;)V") } plugin/src/main/kotlin/com/logger/plugin/CustomClassVisitor.kt Printstream Ref String
  62. InstructionAdapter(this).apply { -/ method trace printing code invokestatic("j/l/System","currentTimeMillis","()J") store(2, LONG_TYPE)

    } Value of currentTimeMillis plugin/src/main/kotlin/com/logger/plugin/CustomClassVisitor.kt
  63. InstructionAdapter(this).apply { -/ method trace printing code invokestatic("j/l/System","currentTimeMillis","()J") store(2, LONG_TYPE)

    } plugin/src/main/kotlin/com/logger/plugin/CustomClassVisitor.kt Value of currentTimeMillis
  64. object : MethodVisitor(Opcodes.ASM5, mv) { override fun visitCode() { super.visitCode()

    InstructionAdapter(this).apply { TODO("on method entry") } } override fun visitInsn(opcode: Int) { when (opcode) { RETURN, ARETURN, IRETURN -> { InstructionAdapter(this).apply { TODO("on method exit") } } } super.visitInsn(opcode) } } plugin/src/main/kotlin/com/logger/plugin/CustomClassVisitor.kt
  65. InstructionAdapter(this).apply{ getstatic("j/l/System", "out", "Lj/io/PrintStream;") anew("java/lang/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇠

    ${function.name} [ran in ") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/StringBuilder;") invokestatic("j/l/System", "currentTimeMillis", "()J") } Printstream Ref StringBuilder Ref Value of currentTimeMillis plugin/src/main/kotlin/com/logger/plugin/CustomClassVisitor.kt
  66. InstructionAdapter(this).apply{ getstatic("j/l/System", "out", "Lj/io/PrintStream;") anew("java/lang/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇠

    ${function.name} [ran in ") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/StringBuilder;") invokestatic("j/l/System", "currentTimeMillis", "()J") load(2, LONG_TYPE) } Printstream Ref StringBuilder Ref Value of currentTimeMillis Start Time plugin/src/main/kotlin/com/logger/plugin/CustomClassVisitor.kt
  67. InstructionAdapter(this).apply{ getstatic("j/l/System", "out", "Lj/io/PrintStream;") anew("java/lang/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇠

    ${function.name} [ran in ") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/StringBuilder;") invokestatic("j/l/System", "currentTimeMillis", "()J") load(2, LONG_TYPE) sub(LONG_TYPE) } Printstream Ref StringBuilder Ref plugin/src/main/kotlin/com/logger/plugin/CustomClassVisitor.kt Value of currentTimeMillis Start Time
  68. InstructionAdapter(this).apply{ getstatic("j/l/System", "out", "Lj/io/PrintStream;") anew("java/lang/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇠

    ${function.name} [ran in ") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/StringBuilder;") invokestatic("j/l/System", "currentTimeMillis", "()J") load(2, LONG_TYPE) sub(LONG_TYPE) } Printstream Ref StringBuilder Ref (Current - Start) time plugin/src/main/kotlin/com/logger/plugin/CustomClassVisitor.kt
  69. InstructionAdapter(this).apply{ getstatic("j/l/System", "out", "Lj/io/PrintStream;") anew("java/lang/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇠

    ${function.name} [ran in ") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/StringBuilder;") invokestatic("j/l/System", "currentTimeMillis", "()J") load(2, LONG_TYPE) sub(LONG_TYPE) invokevirtual("j/l/StringBuilder", "append", "(J)Lj/l/SB;") } Printstream Ref plugin/src/main/kotlin/com/logger/plugin/CustomClassVisitor.kt StringBuilder Ref (Current - Start) time
  70. InstructionAdapter(this).apply{ getstatic("j/l/System", "out", "Lj/io/PrintStream;") anew("java/lang/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇠

    ${function.name} [ran in ") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/StringBuilder;") invokestatic("j/l/System", "currentTimeMillis", "()J") load(2, LONG_TYPE) sub(LONG_TYPE) invokevirtual("j/l/StringBuilder", "append", "(J)Lj/l/SB;") } Printstream Ref StringBuilder Ref plugin/src/main/kotlin/com/logger/plugin/CustomClassVisitor.kt
  71. InstructionAdapter(this).apply{ getstatic("j/l/System", "out", "Lj/io/PrintStream;") anew("java/lang/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇠

    ${function.name} [ran in ") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/StringBuilder;") invokestatic("j/l/System", "currentTimeMillis", "()J") load(2, LONG_TYPE) sub(LONG_TYPE) invokevirtual("j/l/StringBuilder", "append", "(J)Lj/l/SB;") visitLdcInsn(" ms]") } Printstream Ref StringBuilder Ref “ ms]” plugin/src/main/kotlin/com/logger/plugin/CustomClassVisitor.kt
  72. InstructionAdapter(this).apply{ getstatic("j/l/System", "out", "Lj/io/PrintStream;") anew("java/lang/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇠

    ${function.name} [ran in ") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/StringBuilder;") invokestatic("j/l/System", "currentTimeMillis", "()J") load(2, LONG_TYPE) sub(LONG_TYPE) invokevirtual("j/l/StringBuilder", "append", "(J)Lj/l/SB;") visitLdcInsn(" ms]") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/SB;") } Printstream Ref plugin/src/main/kotlin/com/logger/plugin/CustomClassVisitor.kt StringBuilder Ref “ ms]”
  73. InstructionAdapter(this).apply{ getstatic("j/l/System", "out", "Lj/io/PrintStream;") anew("java/lang/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇠

    ${function.name} [ran in ") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/StringBuilder;") invokestatic("j/l/System", "currentTimeMillis", "()J") load(2, LONG_TYPE) sub(LONG_TYPE) invokevirtual("j/l/StringBuilder", "append", "(J)Lj/l/SB;") visitLdcInsn(" ms]") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/SB;") } Printstream Ref StringBuilder Ref plugin/src/main/kotlin/com/logger/plugin/CustomClassVisitor.kt
  74. InstructionAdapter(this).apply{ getstatic("j/l/System", "out", "Lj/io/PrintStream;") anew("java/lang/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇠

    ${function.name} [ran in ") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/StringBuilder;") invokestatic("j/l/System", "currentTimeMillis", "()J") load(2, LONG_TYPE) sub(LONG_TYPE) invokevirtual("j/l/StringBuilder", "append", "(J)Lj/l/SB;") visitLdcInsn(" ms]") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/SB;") invokevirtual("j/l/StringBuilder", "toString", "()Lj/l/String;") } Printstream Ref plugin/src/main/kotlin/com/logger/plugin/CustomClassVisitor.kt StringBuilder Ref
  75. InstructionAdapter(this).apply{ getstatic("j/l/System", "out", "Lj/io/PrintStream;") anew("java/lang/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇠

    ${function.name} [ran in ") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/StringBuilder;") invokestatic("j/l/System", "currentTimeMillis", "()J") load(2, LONG_TYPE) sub(LONG_TYPE) invokevirtual("j/l/StringBuilder", "append", "(J)Lj/l/SB;") visitLdcInsn(" ms]") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/SB;") invokevirtual("j/l/StringBuilder", "toString", "()Lj/l/String;") } Printstream Ref String plugin/src/main/kotlin/com/logger/plugin/CustomClassVisitor.kt
  76. InstructionAdapter(this).apply{ getstatic("j/l/System", "out", "Lj/io/PrintStream;") anew("java/lang/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇠

    ${function.name} [ran in ") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/StringBuilder;") invokestatic("j/l/System", "currentTimeMillis", "()J") load(2, LONG_TYPE) sub(LONG_TYPE) invokevirtual("j/l/StringBuilder", "append", "(J)Lj/l/SB;") visitLdcInsn(" ms]") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/SB;") invokevirtual("j/l/StringBuilder", "toString", "()Lj/l/String;") invokevirtual("j/io/PrintStream", "println", "(Lj/l/String;)V") } plugin/src/main/kotlin/com/logger/plugin/CustomClassVisitor.kt Printstream Ref String
  77. Resources • Transform API Documentation • Github • Hunter Samples

    • KotlinConf Talks - Writing you First Kotlin Compiler Plugin • https://www.baeldung.com/java-asm • https://dzone.com/articles/introduction-to-java-bytecode