• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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