1 // Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file 2 // for details. All rights reserved. Use of this source code is governed by a 3 // BSD-style license that can be found in the LICENSE file. 4 package com.android.tools.r8.graph; 5 6 import com.android.tools.r8.ir.code.IRCode; 7 import com.android.tools.r8.ir.code.ValueNumberGenerator; 8 import com.android.tools.r8.ir.conversion.IRBuilder; 9 import com.android.tools.r8.ir.conversion.JarSourceCode; 10 import com.android.tools.r8.jar.JarRegisterEffectsVisitor; 11 import com.android.tools.r8.naming.ClassNameMapper; 12 import com.android.tools.r8.utils.InternalOptions; 13 import java.io.PrintWriter; 14 import java.io.StringWriter; 15 import java.util.IdentityHashMap; 16 import org.objectweb.asm.ClassReader; 17 import org.objectweb.asm.ClassVisitor; 18 import org.objectweb.asm.MethodVisitor; 19 import org.objectweb.asm.Opcodes; 20 import org.objectweb.asm.commons.JSRInlinerAdapter; 21 import org.objectweb.asm.tree.MethodNode; 22 import org.objectweb.asm.util.Textifier; 23 import org.objectweb.asm.util.TraceMethodVisitor; 24 25 public class JarCode extends Code { 26 27 public static class ReparseContext { 28 29 // This will hold the content of the whole class. Once all the methods of the class are swapped 30 // from this to the actual JarCode, no other references would be left and the content can be 31 // GC'd. 32 public byte[] classCache; 33 public DexProgramClass owner; 34 private IdentityHashMap<DexMethod, JarCode> lookupMap = new IdentityHashMap<>(); 35 } 36 37 private final DexType clazz; 38 private MethodNode node; 39 private ReparseContext context; 40 41 private final JarApplicationReader application; 42 JarCode(DexMethod method, ReparseContext context, JarApplicationReader application)43 public JarCode(DexMethod method, ReparseContext context, JarApplicationReader application) { 44 this.clazz = method.getHolder(); 45 this.context = context; 46 this.application = application; 47 context.lookupMap.put(method, this); 48 } 49 50 @Override isJarCode()51 public boolean isJarCode() { 52 return true; 53 } 54 55 @Override asJarCode()56 public JarCode asJarCode() { 57 return this; 58 } 59 60 @Override computeHashCode()61 protected int computeHashCode() { 62 triggerDelayedParsingIfNeccessary(); 63 return node.hashCode(); 64 } 65 66 @Override computeEquals(Object other)67 protected boolean computeEquals(Object other) { 68 triggerDelayedParsingIfNeccessary(); 69 if (this == other) { 70 return true; 71 } 72 if (other instanceof JarCode) { 73 JarCode o = (JarCode) other; 74 o.triggerDelayedParsingIfNeccessary(); 75 // TODO(zerny): This amounts to object equality. 76 return node.equals(o.node); 77 } 78 return false; 79 } 80 81 @Override buildIR(DexEncodedMethod encodedMethod, InternalOptions options)82 public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options) { 83 triggerDelayedParsingIfNeccessary(); 84 JarSourceCode source = new JarSourceCode(clazz, node, application); 85 IRBuilder builder = new IRBuilder(encodedMethod, source, options); 86 return builder.build(); 87 } 88 buildIR(DexEncodedMethod encodedMethod, ValueNumberGenerator generator, InternalOptions options)89 public IRCode buildIR(DexEncodedMethod encodedMethod, ValueNumberGenerator generator, 90 InternalOptions options) { 91 triggerDelayedParsingIfNeccessary(); 92 JarSourceCode source = new JarSourceCode(clazz, node, application); 93 IRBuilder builder = new IRBuilder(encodedMethod, source, generator, options); 94 return builder.build(); 95 } 96 97 98 @Override registerReachableDefinitions(UseRegistry registry)99 public void registerReachableDefinitions(UseRegistry registry) { 100 triggerDelayedParsingIfNeccessary(); 101 node.instructions.accept(new JarRegisterEffectsVisitor(clazz, registry, application)); 102 } 103 104 @Override toString()105 public String toString() { 106 triggerDelayedParsingIfNeccessary(); 107 TraceMethodVisitor visitor = new TraceMethodVisitor(new Textifier()); 108 node.accept(visitor); 109 StringWriter writer = new StringWriter(); 110 visitor.p.print(new PrintWriter(writer)); 111 return writer.toString(); 112 } 113 114 @Override toString(DexEncodedMethod method, ClassNameMapper naming)115 public String toString(DexEncodedMethod method, ClassNameMapper naming) { 116 return toString(); 117 } 118 triggerDelayedParsingIfNeccessary()119 private void triggerDelayedParsingIfNeccessary() { 120 if (context != null) { 121 // The SecondVistor is in charge of setting the context to null. 122 DexProgramClass owner = context.owner; 123 new ClassReader(context.classCache).accept(new SecondVisitor(context, application), 124 ClassReader.SKIP_FRAMES); 125 assert verifyNoReparseContext(owner); 126 } 127 } 128 129 /** 130 * Fills the MethodNodes of all the methods in the class and removes the ReparseContext. 131 */ 132 private static class SecondVisitor extends ClassVisitor { 133 134 private final ReparseContext context; 135 private final JarApplicationReader application; 136 SecondVisitor(ReparseContext context, JarApplicationReader application)137 public SecondVisitor(ReparseContext context, JarApplicationReader application) { 138 super(Opcodes.ASM5); 139 this.context = context; 140 this.application = application; 141 } 142 143 @Override visitMethod(int access, String name, String desc, String signature, String[] exceptions)144 public MethodVisitor visitMethod(int access, String name, String desc, String signature, 145 String[] exceptions) { 146 MethodNode node = new JSRInlinerAdapter(null, access, name, desc, signature, exceptions); 147 JarCode code = context.lookupMap.get(application.getMethod(context.owner.type, name, desc)); 148 if (code != null) { 149 code.context = null; 150 code.node = node; 151 return node; 152 } 153 return null; 154 } 155 } 156 verifyNoReparseContext(DexProgramClass owner)157 private static boolean verifyNoReparseContext(DexProgramClass owner) { 158 for (DexEncodedMethod method : owner.virtualMethods()) { 159 Code code = method.getCode(); 160 if (code != null && code.isJarCode()) { 161 if (code.asJarCode().context != null) { 162 return false; 163 } 164 } 165 } 166 167 for (DexEncodedMethod method : owner.directMethods()) { 168 Code code = method.getCode(); 169 if (code != null && code.isJarCode()) { 170 if (code.asJarCode().context != null) { 171 return false; 172 } 173 } 174 } 175 return true; 176 } 177 } 178