1 // Copyright 2017 The Bazel Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 package com.google.devtools.build.android.desugar.io; 15 16 import java.io.IOException; 17 import java.io.InputStream; 18 import javax.annotation.Nullable; 19 import org.objectweb.asm.Attribute; 20 import org.objectweb.asm.ClassReader; 21 import org.objectweb.asm.ClassVisitor; 22 import org.objectweb.asm.ClassWriter; 23 import org.objectweb.asm.Opcodes; 24 import org.objectweb.asm.commons.ClassRemapper; 25 import org.objectweb.asm.commons.Remapper; 26 27 /** Utility class to prefix or unprefix class names of core library classes */ 28 public class CoreLibraryRewriter { 29 private final String prefix; 30 CoreLibraryRewriter(String prefix)31 public CoreLibraryRewriter(String prefix) { 32 this.prefix = prefix; 33 } 34 35 /** 36 * Factory method that returns either a normal ClassReader if prefix is empty, or a ClassReader 37 * with a ClassRemapper that prefixes class names of core library classes if prefix is not empty. 38 */ reader(InputStream content)39 public ClassReader reader(InputStream content) throws IOException { 40 if (prefix.isEmpty()) { 41 return new ClassReader(content); 42 } else { 43 return new PrefixingClassReader(content, prefix); 44 } 45 } 46 47 /** 48 * Factory method that returns a ClassVisitor that delegates to a ClassWriter, removing prefix 49 * from core library class names if it is not empty. 50 */ writer(int flags)51 public UnprefixingClassWriter writer(int flags) { 52 return new UnprefixingClassWriter(flags); 53 } 54 shouldPrefix(String typeName)55 static boolean shouldPrefix(String typeName) { 56 return (typeName.startsWith("java/") || typeName.startsWith("sun/")) && !except(typeName); 57 } 58 except(String typeName)59 private static boolean except(String typeName) { 60 if (typeName.startsWith("java/lang/invoke/")) { 61 return true; 62 } 63 64 switch (typeName) { 65 // Autoboxed types 66 case "java/lang/Boolean": 67 case "java/lang/Byte": 68 case "java/lang/Character": 69 case "java/lang/Double": 70 case "java/lang/Float": 71 case "java/lang/Integer": 72 case "java/lang/Long": 73 case "java/lang/Number": 74 case "java/lang/Short": 75 76 // Special types 77 case "java/lang/Class": 78 case "java/lang/Object": 79 case "java/lang/String": 80 case "java/lang/Throwable": 81 return true; 82 83 default: // fall out 84 } 85 86 return false; 87 } 88 getPrefix()89 public String getPrefix() { 90 return prefix; 91 } 92 93 /** Removes prefix from class names */ unprefix(String typeName)94 public String unprefix(String typeName) { 95 if (prefix.isEmpty() || !typeName.startsWith(prefix)) { 96 return typeName; 97 } 98 return typeName.substring(prefix.length()); 99 } 100 101 /** ClassReader that prefixes core library class names as they are read */ 102 private static class PrefixingClassReader extends ClassReader { 103 private final String prefix; 104 PrefixingClassReader(InputStream content, String prefix)105 PrefixingClassReader(InputStream content, String prefix) throws IOException { 106 super(content); 107 this.prefix = prefix; 108 } 109 110 @Override accept(ClassVisitor cv, Attribute[] attrs, int flags)111 public void accept(ClassVisitor cv, Attribute[] attrs, int flags) { 112 cv = 113 new ClassRemapper( 114 cv, 115 new Remapper() { 116 @Override 117 public String map(String typeName) { 118 return prefix(typeName); 119 } 120 }); 121 super.accept(cv, attrs, flags); 122 } 123 124 @Override getClassName()125 public String getClassName() { 126 return prefix(super.getClassName()); 127 } 128 129 @Override getSuperName()130 public String getSuperName() { 131 String result = super.getSuperName(); 132 return result != null ? prefix(result) : null; 133 } 134 135 @Override getInterfaces()136 public String[] getInterfaces() { 137 String[] result = super.getInterfaces(); 138 for (int i = 0, len = result.length; i < len; ++i) { 139 result[i] = prefix(result[i]); 140 } 141 return result; 142 } 143 144 /** Prefixes core library class names with prefix. */ prefix(String typeName)145 private String prefix(String typeName) { 146 if (shouldPrefix(typeName)) { 147 return prefix + typeName; 148 } 149 return typeName; 150 } 151 } 152 153 /** 154 * ClassVisitor that delegates to a ClassWriter, but removes a prefix as each class is written. 155 * The unprefixing is optimized out if prefix is empty. 156 */ 157 public class UnprefixingClassWriter extends ClassVisitor { 158 private final ClassWriter writer; 159 160 private String finalClassName; 161 UnprefixingClassWriter(int flags)162 UnprefixingClassWriter(int flags) { 163 super(Opcodes.ASM6); 164 this.writer = new ClassWriter(flags); 165 this.cv = this.writer; 166 if (!prefix.isEmpty()) { 167 this.cv = 168 new ClassRemapper( 169 this.writer, 170 new Remapper() { 171 @Override 172 public String map(String typeName) { 173 return unprefix(typeName); 174 } 175 }); 176 } 177 } 178 179 /** Returns the (unprefixed) name of the class once written. */ 180 @Nullable getClassName()181 public String getClassName() { 182 return finalClassName; 183 } 184 toByteArray()185 public byte[] toByteArray() { 186 return writer.toByteArray(); 187 } 188 189 @Override visit( int version, int access, String name, String signature, String superName, String[] interfaces)190 public void visit( 191 int version, 192 int access, 193 String name, 194 String signature, 195 String superName, 196 String[] interfaces) { 197 finalClassName = unprefix(name); 198 super.visit(version, access, name, signature, superName, interfaces); 199 } 200 } 201 } 202