Slide 1

Slide 1 text

Java Ͱ Bytecode Weaving HACKER TACKLE [email protected] 2016/9/10 ࢁ㟒 ੣

Slide 2

Slide 2 text

Makoto Yamazaki Realm Inc. / uPhyca Inc. [email protected] @zaki50

Slide 3

Slide 3 text

Bytecode weaving? [email protected]

Slide 4

Slide 4 text

[email protected] weaving? ৫Δ ฤΉ ࡞Δ ૊Έ্͛Δ

Slide 5

Slide 5 text

[email protected] Bytecode weavingͱ͸ ίϯύΠϧ࣌΍όΠτίʔυͷϩʔυ࣌ʹ όΠτίʔυʹखΛՃ͑Δ͜ͱ όΠτίʔυ͸ɺJava VM ͷ໋ྩηοτͷ͜ͱͰ͢

Slide 6

Slide 6 text

[email protected] ΞδΣϯμ RealmͰͷ۩ମྫ ࣮૷ํ๏ ͳʹ͕Ͱ͖Δͷ͔

Slide 7

Slide 7 text

[email protected] ͳʹ͕Ͱ͖Δͷ͔ όΠτίʔυͰදݱͰ͖Δ͜ͱͰ͋Ε͹ͳΜͰ΋

Slide 8

Slide 8 text

[email protected] ۩ମతʹ͸ʁ ಛఆͷॲཧͷফڈ Ϋϥε/ϝιουͷ௥Ճ ॲཧͷஔ͖׵͑ Կ͔ͷϦετΞοϓ

Slide 9

Slide 9 text

[email protected] ۩ମతʹ͸ʁ ϦϦʔεͰlogΛؔ࿈Λ࡟আ Realm retrolambda ServiceLoaderͷ୅ΘΓ

Slide 10

Slide 10 text

Slide 11

Slide 11 text

[email protected] https://realm.io/

Slide 12

Slide 12 text

[email protected] ௚ײతͳϞσϧఆٛ public class Article extends RealmObject {
 @PrimaryKey
 public String id;
 public String title;
 public String contents;
 public Date createdAt;
 public Date modifiedAt;
 public User user;
 public RealmList comments;
 }

Slide 13

Slide 13 text

[email protected] mobile database ? ܞଳ୺຤΍λϒϨοτ಺Ͱಈ͘σʔλϕʔε

Slide 14

Slide 14 text

ϝϞϦʔ͕ݶΒΕͨঢ়گ [email protected] CPUೳྗ͕ݶΒΕͨঢ়گ Why mobile database? → ஗Ԇϩʔυ → θϩίϐʔ

Slide 15

Slide 15 text

[email protected] θϩίϐʔ/஗Ԇϩʔυ Realm realm = ... // データの取得 RealmResults articles; articles = realm.where(Article.class).findAll(); Article article = articles.first(); article.title; // "Hacker Tackleで発表してきた" // よくある使い方 ListAdapter adapter = new ArticleAdapter(articles); listview.setAdapter(adapter);

Slide 16

Slide 16 text

[email protected] θϩίϐʔ/஗Ԇϩʔυ

Slide 17

Slide 17 text

Ξϊςʔγϣϯϓϩηοα [email protected] ϑΟʔϧυͷ஋ͷ஗Ԇϩʔυͷ࣮ݱ (Ҏલͷ࣮૷) (JSR 269)

Slide 18

Slide 18 text

[email protected] Ҏલͷ࣮૷ public class Article extends RealmObject {
 @PrimaryKey
 private String id; // その他のフィールド public String getId() { return id; }
 public void setId(String id) { this.id = id; } // その他のgetter/setter
 }

Slide 19

Slide 19 text

[email protected] Ҏલͷ࣮૷ public class ArticleRealmProxy extends Article { private Row row; @Override
 public String getId() { return row.get(1); }
 public void setId(String id) { row.set(1, id); }
 } by Ξϊςʔγϣϯϓϩηοα

Slide 20

Slide 20 text

ଟ͘ͷ੍໿͕ଘࡏ 1. ϑΟʔϧυ΁ͷ௚઀ΞΫηεېࢭ 2. getter/setter͕ඞਢ 3. getter/setter͸஋ͷग़͠ೖΕͷΈ 4. ͦͷଞͷϝιουͷېࢭ(1Λపఈ͢ΔͨΊ) 5. ... [email protected]

Slide 21

Slide 21 text

Ξϊςʔγϣϯϓϩηοα [email protected] + Bytecode weaving ੍໿Λͳͨ͘͢Ίʹʂ ϑΟʔϧυͷ஋ͷ஗Ԇϩʔυͷ࣮ݱ

Slide 22

Slide 22 text

[email protected] ࠓͷRealm Java public class Article extends RealmObject {
 @PrimaryKey
 public String id;
 public String title;
 public String contents;
 public Date createdAt;
 public Date modifiedAt;
 public User user;
 public RealmList comments;
 }

Slide 23

Slide 23 text

[email protected] Ҏલͷ࣮૷(࠶ܝ) public class Article extends RealmObject {
 @PrimaryKey
 private String id; private String title;
 private String contents;
 private Date createdAt;
 private Date modifiedAt;
 private User user;
 private RealmList comments;
 public String getId() { return id; }
 public void setId(String id) { this.id = id; } public String getTitle() { return title; }
 public void setTitle(String title) { this.title = title; } public String getContents() { contents ; } public void setContents(String contents) { contents = contents; } // かききれない
 }

Slide 24

Slide 24 text

[email protected] ࠓͷRealm Java public class ArticleRealmProxy extends Article { private Row row; public String realmGet$id() { return row.get(1); }
 public void realmSet$id(String id) { row.set(1, id); }
 } by Ξϊςʔγϣϯϓϩηοα

Slide 25

Slide 25 text

[email protected] ࠓͷRealm Java public class Article extends RealmObject { @PrimaryKey
 public String id; public String realmGet$id() { return id; }
 public void realmSet$id(String id) { return row.set(1, id); }
 } by bytecode weaving ௥Ճʂ

Slide 26

Slide 26 text

[email protected] ࠓͷRealm Java public class Article extends RealmObject {
 @PrimaryKey
 public String id; public RealmList comments; public boolean hasComments() { return !comments.isEmpty(); } } by bytecode weaving this.realmGet$comments()

Slide 27

Slide 27 text

[email protected] ࠓͷRealm Java RealmResults results = realm.where(Article.class).findAll();
 Article article = results.first();
 String title = article.title; String title = article.realmGet$title(); by bytecode weaving

Slide 28

Slide 28 text

ଟ͘ͷ੍໿͕ղফʂ 1. ϑΟʔϧυ΁ͷ௚઀ΞΫηεېࢭ 2. getter/setter͕ඞਢ 3. getter/setter͸஋ͷग़͠ೖΕͷΈ 4. ͦͷଞͷϝιουͷېࢭ(1Λపఈ͢ΔͨΊ) 5. ... [email protected]

Slide 29

Slide 29 text

Transform API [email protected] Javassist ࣮૷ํ๏

Slide 30

Slide 30 text

[email protected] Transform API project.android.registerTransform(new RealmTransformer(project)) in build.gradle $ ./gradlew assemble ... :app:compileDebugSources :app:prePackageMarkerForDebug :app:unzipJacocoAgent :app:transformClassesWithJacocoForDebug :app:transformClassesWithRealmTransformerForDebug :app:transformClassesWithDexForDebug ...

Slide 31

Slide 31 text

[email protected] Transform API @Beta public abstract class Transform { // many other methods here public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException { } }

Slide 32

Slide 32 text

[email protected] Transform API class RealmTransformer extends Transform { @Override public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException { Context context = transformInvocation.getContext(); Collection inputs = transformInvocation.getInputs(); Collection referencedInputs; referencedInputs = transformInvocation.getReferencedInputs(); TransformOutputProvider outputProvider = transformInvocation.getOutputProvider(); boolean isIncremental = transformInvocation.isIncremental(); ... } }

Slide 33

Slide 33 text

[email protected] Javassist ந৅౓ ௿: ASM, Apache commons BCEL ந৅౓ த : javassist, CGLIB ந৅౓ ߴ : Retro lambda, AspectJ

Slide 34

Slide 34 text

[email protected] Javassist dependencies { compile 'org.javassist:javassist:3.20.0-GA' }

Slide 35

Slide 35 text

[email protected] Javassist CtClassΛ͍͡Δ ॻ͖ग़͢/ϩʔυ͢Δ ClassPoolΛ࡞Δ

Slide 36

Slide 36 text

[email protected] Realmͷྫ @Override void transform(...) throws IOException, ... { // Create and populate the Javassist class pool ClassPool classPool = createClassPool(inputs, referencedInputs) inputModelClasses.each { BytecodeModifier.addRealmAccessors(it) } inputClassNames.each { def ctClass = classPool.getCtClass(it) BytecodeModifier.useRealmAccessors(ctClass, allManagedFields, allModelClasses) ctClass.writeFile(getOutputFile(outputProvider).canonicalPath) } }

Slide 37

Slide 37 text

[email protected] CtClassΛ͍͡Δ ॻ͖׵͑Δܥ ৽ͨʹ࡞Δܥ

Slide 38

Slide 38 text

[email protected] Ϋϥε/ϑΟʔϧυͷ௥Ճ // フィールド作成 CtClass strClass = pool.get("java.lang.String"); CtField barField = new CtField(CtClass.intType, "bar", strClass); barField.setModifiers(Modifier.PUBLIC); // クラス作成 CtClass fooClass = pool.makeClass("Foo"); // クラスへフィールド追加 fooClass.addField(f); // メソッド追加 CtMethod method = CtNewMethod.make(Modifier.PUBLIC, CtClass.booleanType, "isValid", new CtClass[0], new CtClass[0], "{return true;}", fooClass) fooClass.addMethod(method)

Slide 39

Slide 39 text

[email protected] ม਺ $0, $1, $2, ... $args $$ $cflow(...) $r $w $_ $sig $type $class thisおよび引き数 引き数の値の配列(Object[]型) $1,$2,... に展開される useCflow(String)で宣言した変数の値。 再帰呼び出しごとに++される 返り値の型(キャスト用) primitiveに対するキャストの形で書くとラッパー型に展開 Integer i = ($w)1; メソッドの返り値。insertAfter()などで使う メソッドで宣言された引き数のClassオブジェクトの配列 メソッドで宣言された返り値のClassオブジェクト 現在処理対象としている型のClassオブジェクト https://jboss-javassist.github.io/javassist/tutorial/tutorial2.html

Slide 40

Slide 40 text

[email protected] ϑΟʔϧυΞΫηεͷॻ͖׵͑ public class FooEditor extends ExprEditor { void edit(FieldAccess fieldAccess) { if (fieldAccess.isReader()) { fieldAccess.replace("$_ = ($0 == this) ? $0.foo() : $0.foo;"); } } } CtBehavior someMethod = ...; someMethod.instrument(new FooEditor());

Slide 41

Slide 41 text

[email protected] ม਺(ϑΟʔϧυΞΫηε) $0 $1 $_ $r $class $type フィールドの属するインスタンス 書き込みアクセスの際に代入されようとしている値 読込みアクセスの際に、読み込んだ値が入る予定の変数 読込みアクセスの際のフィールドの型 フィールドが属するクラスのClassオブジェクト フィールドの型 https://jboss-javassist.github.io/javassist/tutorial/tutorial2.html

Slide 42

Slide 42 text

[email protected] ϝιουΞΫηεͷॻ͖׵͑ public class FooEditor extends ExprEditor { void edit(MethodCall m) { m("$_ = ($0 == this) ? $0.foo() : $0.foo;"); } } CtBehavior someMethod = ...; someMethod.instrument(new FooEditor());

Slide 43

Slide 43 text

[email protected] ม਺(ϝιουݺͼग़͠) $0 $1, $2, ... $_ $r $class $sig $type メソッドのレシーバー 引き数の値 メソッドの返り値を入れる変数 返り値の型 メソッドが属するクラスのClassオブジェクト メソッドで宣言された引き数のClassオブジェクトの配列 返り値の型 https://jboss-javassist.github.io/javassist/tutorial/tutorial2.html

Slide 44

Slide 44 text

[email protected] ͦͷଞͷॻ͖׵͑ ίϯετϥΫλݺͼग़͠ instanceof, cast new, new[] ྫ֎ͷcatch

Slide 45

Slide 45 text

[email protected] RealmͰͷ࣮ࡍͷίʔυ inputModelClasses.each { BytecodeModifier.addRealmAccessors(it) } inputClassNames.each { def ctClass = classPool.getCtClass(it) BytecodeModifier.useRealmAccessors(ctClass, allManagedFields, allModelClasses) ctClass.writeFile(getOutputFile(outputProvider).canonicalPath) }

Slide 46

Slide 46 text

[email protected] RealmͰͷ࣮ࡍͷίʔυ public static void addRealmAccessors(CtClass clazz) {
 def methods = clazz.getDeclaredMethods()*.name
 clazz.declaredFields.each { CtField field ->
 if (!Modifier.isStatic(field.getModifiers()) && !field.hasAnnotation(Ignore.class)) {
 if (!methods.contains("realmGet\$${field.name}")) {
 clazz.addMethod(CtNewMethod.getter("realmGet\$${field.name}", field))
 }
 if (!methods.contains("realmSet\$${field.name}")) {
 clazz.addMethod(CtNewMethod.setter("realmSet\$${field.name}", field))
 }
 }
 }
 }

Slide 47

Slide 47 text

[email protected] RealmͰͷ࣮ࡍͷίʔυ public static void useRealmAccessors(CtClass clazz, List managedFields, List modelClasses) {
 clazz.getDeclaredBehaviors().each { behavior ->
 if (( behavior instanceof CtMethod &&
 !behavior.name.startsWith('realmGet$') &&
 !behavior.name.startsWith('realmSet$')
 ) || (
 behavior instanceof CtConstructor &&
 !modelClasses.contains(clazz)
 )) {
 behavior.instrument(new FieldAccessToAccessorConverter(managedFields, clazz, behavior))
 }
 }
 }

Slide 48

Slide 48 text

[email protected] RealmͰͷ࣮ࡍͷίʔυ private static class FieldAccessToAccessorConverter extends ExprEditor {
 final List managedFields
 final CtClass ctClass
 final CtBehavior behavior
 
 @Override
 void edit(FieldAccess fieldAccess) throws CannotCompileException {
 def isRealmFieldAccess = managedFields.find {
 fieldAccess.className.equals(it.declaringClass.name) && fieldAccess.fieldName.equals(it.name)
 }
 if (isRealmFieldAccess != null) {
 def fieldName = fieldAccess.fieldName
 if (fieldAccess.isReader()) {
 fieldAccess.replace('$_ = $0.realmGet$' + fieldName + '();')
 } else if (fieldAccess.isWriter()) {
 fieldAccess.replace('$0.realmSet$' + fieldName + '($1);')
 }
 }
 }
 }

Slide 49

Slide 49 text

@RealmClass public class Dog implements RealmModel { public String name; public int age; } Realm realm = Realm.getDefaultInstance(); realm.beginTransaction(); Dog dog = realm.createObject(Dog.class); dog.name = "Rex"; dog.age = 1; realm.commitTransaction(); RealmResults pups = realm.where(Dog.class) .lessThan("age", 2) .findAll(); [email protected]