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; 15 16 import java.io.IOException; 17 import java.io.InputStream; 18 import org.objectweb.asm.Attribute; 19 import org.objectweb.asm.ClassReader; 20 import org.objectweb.asm.ClassVisitor; 21 import org.objectweb.asm.ClassWriter; 22 import org.objectweb.asm.MethodVisitor; 23 import org.objectweb.asm.Opcodes; 24 import org.objectweb.asm.commons.ClassRemapper; 25 import org.objectweb.asm.commons.MethodRemapper; 26 import org.objectweb.asm.commons.Remapper; 27 28 /** Utility class to prefix or unprefix class names of core library classes */ 29 class CoreLibraryRewriter { 30 private final String prefix; 31 CoreLibraryRewriter(String prefix)32 public CoreLibraryRewriter(String prefix) { 33 this.prefix = prefix; 34 } 35 36 /** 37 * Factory method that returns either a normal ClassReader if prefix is empty, or a ClassReader 38 * with a ClassRemapper that prefixes class names of core library classes if prefix is not empty. 39 */ reader(InputStream content)40 public ClassReader reader(InputStream content) throws IOException { 41 if (prefix.length() != 0) { 42 return new PrefixingClassReader(content); 43 } else { 44 return new ClassReader(content); 45 } 46 } 47 48 /** 49 * Factory method that returns a ClassVisitor that delegates to a ClassWriter, removing prefix 50 * from core library class names if it is not empty. 51 */ writer(int flags)52 public UnprefixingClassWriter writer(int flags) { 53 return new UnprefixingClassWriter(flags); 54 } 55 shouldPrefix(String typeName)56 private static boolean shouldPrefix(String typeName) { 57 return (typeName.startsWith("java/") || typeName.startsWith("sun/")) && !except(typeName); 58 } 59 except(String typeName)60 private static boolean except(String typeName) { 61 if (typeName.startsWith("java/lang/invoke/")) { 62 return true; 63 } 64 65 switch (typeName) { 66 // Autoboxed types 67 case "java/lang/Boolean": 68 case "java/lang/Byte": 69 case "java/lang/Character": 70 case "java/lang/Double": 71 case "java/lang/Float": 72 case "java/lang/Integer": 73 case "java/lang/Long": 74 case "java/lang/Number": 75 case "java/lang/Short": 76 77 // Special types 78 case "java/lang/Class": 79 case "java/lang/Object": 80 case "java/lang/String": 81 case "java/lang/Throwable": 82 return true; 83 84 default: // fall out 85 } 86 87 return false; 88 } 89 90 /** Prefixes core library class names with prefix */ prefix(String typeName)91 public String prefix(String typeName) { 92 if (prefix.length() > 0 && shouldPrefix(typeName)) { 93 return prefix + typeName; 94 } 95 return typeName; 96 } 97 98 /** Removes prefix from class names */ unprefix(String typeName)99 public String unprefix(String typeName) { 100 if (prefix.length() == 0 || !typeName.startsWith(prefix)) { 101 return typeName; 102 } 103 return typeName.substring(prefix.length()); 104 } 105 106 /** ClassReader that prefixes core library class names as they are read */ 107 private class PrefixingClassReader extends ClassReader { PrefixingClassReader(InputStream content)108 PrefixingClassReader(InputStream content) throws IOException { 109 super(content); 110 } 111 112 @Override accept(ClassVisitor cv, Attribute[] attrs, int flags)113 public void accept(ClassVisitor cv, Attribute[] attrs, int flags) { 114 cv = 115 new ClassRemapperWithBugFix( 116 cv, 117 new Remapper() { 118 @Override 119 public String map(String typeName) { 120 return prefix(typeName); 121 } 122 }); 123 super.accept(cv, attrs, flags); 124 } 125 } 126 127 /** 128 * ClassVisitor that delegates to a ClassWriter, but removes a prefix as each class is written. 129 * The unprefixing is optimized out if prefix is empty. 130 */ 131 public class UnprefixingClassWriter extends ClassVisitor { 132 private final ClassWriter writer; 133 UnprefixingClassWriter(int flags)134 UnprefixingClassWriter(int flags) { 135 super(Opcodes.ASM5); 136 this.writer = new ClassWriter(flags); 137 this.cv = this.writer; 138 if (prefix.length() != 0) { 139 this.cv = 140 new ClassRemapperWithBugFix( 141 this.cv, 142 new Remapper() { 143 @Override 144 public String map(String typeName) { 145 return unprefix(typeName); 146 } 147 }); 148 } 149 } 150 toByteArray()151 byte[] toByteArray() { 152 return writer.toByteArray(); 153 } 154 } 155 156 /** ClassRemapper subclass to work around b/36654936 (caused by ASM bug 317785) */ 157 private static class ClassRemapperWithBugFix extends ClassRemapper { 158 ClassRemapperWithBugFix(ClassVisitor cv, Remapper remapper)159 public ClassRemapperWithBugFix(ClassVisitor cv, Remapper remapper) { 160 super(cv, remapper); 161 } 162 163 @Override createMethodRemapper(MethodVisitor mv)164 protected MethodVisitor createMethodRemapper(MethodVisitor mv) { 165 return new MethodRemapper(mv, this.remapper) { 166 167 @Override 168 public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { 169 if (this.mv != null) { 170 mv.visitFrame( 171 type, 172 nLocal, 173 remapEntriesWithBugfix(nLocal, local), 174 nStack, 175 remapEntriesWithBugfix(nStack, stack)); 176 } 177 } 178 179 /** 180 * In {@code FrameNode.accept(MethodVisitor)}, when the frame is Opcodes.F_CHOP, it is 181 * possible that nLocal is greater than 0, and local is null, which causes MethodRemapper to 182 * throw a NPE. So the patch is to make sure that the {@code nLocal<=local.length} and 183 * {@code nStack<=stack.length} 184 */ 185 private Object[] remapEntriesWithBugfix(int n, Object[] entries) { 186 if (entries == null || entries.length == 0) { 187 return entries; 188 } 189 for (int i = 0; i < n; i++) { 190 if (entries[i] instanceof String) { 191 Object[] newEntries = new Object[n]; 192 if (i > 0) { 193 System.arraycopy(entries, 0, newEntries, 0, i); 194 } 195 do { 196 Object t = entries[i]; 197 newEntries[i++] = t instanceof String ? remapper.mapType((String) t) : t; 198 } while (i < n); 199 return newEntries; 200 } 201 } 202 return entries; 203 } 204 }; 205 } 206 } 207 } 208