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

Bytecode manipulation in Realm How and why?

zaki50
August 18, 2016

Bytecode manipulation in Realm How and why?

zaki50

August 18, 2016
Tweet

More Decks by zaki50

Other Decks in Technology

Transcript

  1. [email protected] Bytecode manipulation is... modifying the bytecode at compile time

    or at the loading of the classes. Bytecode is the instruction set of JVM.
  2. [email protected] Realm is Easy 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<Comment> comments;
 }
  3. [email protected] Realm is Fast RealmQuery<Article> query = realm.where(Article.class); query =

    query.contains("title", "realm"); RealmResults<Article> results = query.findAll();
 // results does not contain any actual Article instances here.
 
 Article article = results.first();
 // article does not contain any field values here.
 
 String title = article.title;
 // it looks just a field access. // but it fetches the actual value from the database.

  4. [email protected] Old Realm-java public class Article extends RealmObject {
 @PrimaryKey


    private String id; // other fields public String getId() { return id; }
 public void setId(String id) { this.id = id; }
 } User defines this
  5. [email protected] Old Realm-java public class ArticleRealmProxy extends Article { private

    Row row; @Override
 public String getId() { return row.get(columnInfo.columnIdIndex); }
 public void setId(String id) { row.set(columnInfo.columnIdIndex, id); }
 } Generated by annotation processor
  6. Many restrictions 1. fields must be private 2. must have

    public getters and setters 3. getters and setters must not have a logic 4. no custom methods 5. must be a subclass of RealmObject class 6. and so on... [email protected]
  7. [email protected] Current Realm-java public class ArticleRealmProxy extends Article { private

    Row row; public String realmGet$id() { return row.get(columnInfo.columnIdIndex); }
 public void realmSet$id(String id) { row.set(columnInfo.columnIdIndex, id); }
 } Generated by annotation processor
  8. [email protected] Current Realm-java public class Article extends RealmObject { @PrimaryKey


    public String id; public String realmGet$id() { return return id; }
 public void realmSet$id(String id) { return row.set(columnInfo.columnIdIndex, id); }
 } by bytecode manipulation
  9. [email protected] Current Realm-java RealmResults<Article> results = realm.where(Article.class).findAll();
 Article article =

    results.first();
 String title = article.title; String title = article.realmGet$title(); by bytecode manipulation
  10. [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 ...
  11. [email protected] Transform API @Beta public abstract class Transform { //

    many other methods here public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException { } }
  12. [email protected] Transform API class RealmTransformer extends Transform { @Override public

    void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException { Context context = transformInvocation.getContext(); Collection<TransformInput> inputs = transformInvocation.getInputs(); Collection<TransformInput> referencedInputs; referencedInputs = transformInvocation.getReferencedInputs(); TransformOutputProvider outputProvider = transformInvocation.getOutputProvider(); boolean isIncremental = transformInvocation.isIncremental(); ... } }
  13. [email protected] Javassist Low level: ASM, Apache commons BCEL Middle level

    : javassist, CGLIB High level : Retro lambda, AspectJ many similar libraries
  14. [email protected] Javassist inputModelClasses.each { BytecodeModifier.addRealmAccessors(it) } inputClassNames.each { def ctClass

    = classPool.getCtClass(it) BytecodeModifier.useRealmAccessors(ctClass, allManagedFields, allModelClasses) ctClass.writeFile(getOutputFile(outputProvider).canonicalPath) }
  15. [email protected] Javassist 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))
 }
 }
 }
 }
  16. [email protected] Javassist public static void useRealmAccessors(CtClass clazz, List<CtField> managedFields, List<CtClass>

    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))
 }
 }
 }
  17. [email protected] Javassist private static class FieldAccessToAccessorConverter extends ExprEditor {
 final

    List<CtField> 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);')
 }
 }
 }
 }