ASMをつかってdex前にclass fileを覗き見る

Manipulating Java Class Files with ASM 4 - Part One : Hello World!Androidのプロジェクトにやってみた。

まず /buildSrc/build.gradle *1 に ASM の依存を書く。

apply plugin: 'groovy'

repositories {
    mavenCentral()
}

dependencies {
    compile gradleApi()
    compile localGroovy()

    compile 'org.ow2.asm:asm:5.0.+'
}

要はclass fileをコンパイルした直後、dex file (dalvik executable) を生成するまえにASMのClassVisitorで覗き見てやればいいわけで、moduleのbuild.gradleでこんな感じにするとよい。

android.applicationVariants.all { variant ->
    variant.javaCompile.doLast {
        String path = file('build/classes/debug/com/github/gfx/asmexample/app/MainActivity.class')
        ClassDumper.dump([path])
    }
}

あとは、ClassDumperを書く。今回は buildSrc/src/main/groovy/com/github/gfx/asmexample/ClassDumper.groovy にロジックを書いた。中身は上記ブログそのまま。

package com.github.gfx.asmexample

import org.objectweb.asm.*

public class ClassDumper extends ClassVisitor {

    public static void dump(ArrayList<String> args) throws IOException {
        ClassVisitor visitor = new ClassDumper(Opcodes.ASM5);

        for (String file : args) {
            InputStream ins = new FileInputStream(file);

            new ClassReader(ins).accept(visitor, 0);
        }
    }

    public ClassDumper(int v) {
        super(v);
    }

    @Override
    public void visit(int version, int access, String name,
                      String signature, String superName, String[] interfaces) {
        System.out.println("Visiting class: " + name);
        System.out.println("Class Major Version: " + version);
        System.out.println("Super class: " + superName);
        super.visit(version, access, name, signature, superName, interfaces);
    }

    @Override
    public void visitOuterClass(String owner, String name, String desc) {
        System.out.println("Outer class: " + owner);
        super.visitOuterClass(owner, name, desc);
    }

    @Override
    public AnnotationVisitor visitAnnotation(String desc,
                                             boolean visible) {
        System.out.println("Annotation: " + desc);
        return super.visitAnnotation(desc, visible);
    }

    @Override
    public void visitAttribute(Attribute attr) {
        System.out.println("Class Attribute: " + attr.type);
        super.visitAttribute(attr);
    }

    @Override
    public void visitInnerClass(String name, String outerName,
                                String innerName, int access) {
        System.out.println("Inner Class: " + innerName + " defined in " + outerName);
        super.visitInnerClass(name, outerName, innerName, access);
    }

    @Override
    public FieldVisitor visitField(int access, String name,
                                   String desc, String signature, Object value) {
        System.out.println("Field: " + name + " " + desc + " value:" + value);
        return super.visitField(access, name, desc, signature, value);
    }

    @Override
    public void visitEnd() {
        System.out.println("Ends here");
        super.visitEnd();
    }

    @Override
    public MethodVisitor visitMethod(int access, String name,
                                     String desc, String signature, String[] exceptions) {
        System.out.println("Method: " + name + " " + desc);
        return super.visitMethod(access, name, desc, signature, exceptions);
    }

    @Override
    public void visitSource(String source, String debug) {
        System.out.println("Source: " + source);
        super.visitSource(source, debug);
    }
}

出力結果:

Visiting class: com/github/gfx/asmexample/app/MainActivity
Class Major Version: 51
Super class: android/app/Activity
Source: MainActivity.java
Inner Class: layout defined in com/github/gfx/asmexample/app/R
Inner Class: menu defined in com/github/gfx/asmexample/app/R
Inner Class: id defined in com/github/gfx/asmexample/app/R
Method: <init> ()V
Method: onCreate (Landroid/os/Bundle;)V
Method: onCreateOptionsMenu (Landroid/view/Menu;)Z
Method: onOptionsItemSelected (Landroid/view/MenuItem;)Z
Ends here

*1:プロジェクトごとのGradleプラグインをしっかり書くときにこのbuildSrcを使う